A Practical Python Approach
Public health agencies never have unlimited money. Every year, a health ministry or insurer has to decide how many dollars go toward breast cancer screening, how many toward colorectal screening, how many toward lung cancer screening, and so on — knowing that each additional dollar catches slightly fewer new cases than the one before it. This is a classic concave resource allocation problem, and it’s a great case study for combining optimization theory with Python.
In this article we’ll build a small but realistic model, solve it two different ways (a general-purpose optimizer and a much faster specialized algorithm), and visualize the results — including a 3D view of the benefit landscape.
The core idea: diminishing returns
Each screening program has a saturation point. If you screen almost everyone in a population, you eventually catch nearly every detectable case, and additional spending buys you very little extra benefit. This is naturally modeled with an exponential saturation curve:
$$
B_i(x_i) = a_i \left(1 - e^{-x_i / b_i}\right)
$$
Here $x_i$ is the budget (in units of $10,000) given to program $i$, $a_i$ is the maximum number of cases that program could ever detect (its ceiling), and $b_i$ controls how quickly it approaches that ceiling.
The optimization problem is:
$$
\max_{x_1,\dots,x_n} \sum_{i=1}^{n} a_i\left(1 - e^{-x_i/b_i}\right) \quad \text{subject to} \quad \sum_{i=1}^n x_i = X_{\text{total}}, \quad x_i \ge 0
$$
Because every $B_i$ is concave, this problem has a beautiful classical solution: at the optimum, the marginal benefit of every program must be equal. This is the same “water-filling” principle used in economics and information theory. Formally, there exists a single multiplier $\lambda$ such that:
$$
\frac{dB_i}{dx_i} = \frac{a_i}{b_i} e^{-x_i/b_i} = \lambda \quad \text{for every program with } x_i > 0
$$
Solving this equation for $x_i$ gives a closed form:
$$
x_i(\lambda) = \max\left(0,\ -b_i \ln!\left(\frac{\lambda b_i}{a_i}\right)\right)
$$
and we only need to search for the $\lambda$ that makes $\sum_i x_i(\lambda) = X_{\text{total}}$. That single insight is what lets us build a much faster solver than a generic optimizer.
The concrete example
Let’s imagine a regional health authority with four screening programs and a total annual budget of $1,000,000 (100 units of $10,000).
| Program | Max detectable cases ($a_i$) | Diminishing-returns scale ($b_i$) |
|---|---|---|
| Breast | 120 | 25 |
| Colorectal | 90 | 15 |
| Lung | 150 | 40 |
| Cervical | 60 | 10 |
These numbers are illustrative but structured to reflect reality: lung cancer screening has a high ceiling but also needs more spending to get there (low-dose CT is expensive), while cervical screening saturates quickly with modest spending (Pap smears are cheap and coverage is often already high).
Full source code (Google Colab-ready)
1 | # ========================================================= |
=== SLSQP baseline result ===
Program Budget ($10k) Detected cases
Breast 28.97 82.34
Colorectal 20.73 67.40
Lung 36.48 89.74
Cervical 13.82 44.93
Total detected cases: 284.42
Solve time: 26.39 ms
=== Water-filling result (should match SLSQP) ===
[28.97 20.73 36.48 13.82]
SLSQP: 60 budgets solved in 0.4973 s (8.29 ms/budget)
Vectorized water-filling: 500 budgets solved in 0.0085 s (0.0170 ms/budget)
Code walkthrough
Section 1–2 (data and baseline solver). The four programs’ parameters ($a_i$, $b_i$) are defined as NumPy arrays. benefit() implements the saturation curve; neg_total_benefit() is its negative, since scipy.optimize.minimize only minimizes, never maximizes. We hand this to SLSQP (Sequential Least Squares Programming), a general-purpose constrained optimizer that can handle the equality constraint $\sum x_i = 100$ and the bounds $x_i \ge 0$. This is our “ground truth” baseline — accurate, but relatively slow because at every iteration it computes gradients and refines the solution numerically.
Section 3 (the fast, specialized solver). Instead of treating this as a generic nonlinear program, we exploit the mathematical structure. Because the benefit functions are concave, we know the optimum occurs when every program’s marginal benefit equals the same constant $\lambda$. water_filling_vectorized() searches for that $\lambda$ using bisection: it guesses a value of $\lambda$, computes how much budget each program would want at that marginal value, checks whether the total exceeds the given budget, and narrows the search range accordingly. It does this for a whole array of different total budgets simultaneously, using NumPy broadcasting (lam[:, None] * b_arr[None, :]), so there is no Python-level loop over budgets or over programs — only a fixed 100-iteration bisection loop that runs entirely in optimized NumPy C code.
Section 4 (speed comparison). We sweep 500 different total-budget scenarios (from $10,000 to $3,000,000) to see how the optimal allocation and total benefit change as the budget grows. Running SLSQP once per scenario carries real overhead (numerical differentiation, constraint handling, convergence checks), so we only ran it for 60 sample points to keep the runtime reasonable. The vectorized water-filling method solves all 500 scenarios in the same or less time that SLSQP needs for a single budget, because the entire sweep is one batch of array operations instead of hundreds of independent optimizer calls.
Sections 5–8 (visualization). These build the four charts described below.
Understanding the graphs

Graph 1 — Optimal allocation (bar chart). Shows exactly how the $1,000,000 budget should be split across the four programs at the optimum. Programs with a high ceiling ($a_i$) but that also converge slowly (large $b_i$), like Lung, tend to receive a larger share, while cheap-to-saturate programs like Cervical receive comparatively less once their curve has flattened out.

Graph 2 — Total detected cases vs. total budget. This line traces the overall diminishing-returns curve of the entire screening system, not just one program. Early dollars are extremely productive; as the total budget grows past a certain point, each additional $100,000 buys noticeably fewer additional detected cases. This is exactly the kind of curve a health ministry would want when arguing for (or against) additional funding.

Graph 3 — Marginal benefit curves. This is the most conceptually important chart. Each colored curve shows how much extra benefit one more $10,000 buys a given program, as a function of how much it has already received. The dashed horizontal line marks the common marginal value $\lambda$ at the optimum, and the dotted vertical lines show where each program’s curve crosses that line. Visually, this proves the water-filling principle: money keeps flowing into whichever program currently has the highest marginal payoff, until all four curves cross the same horizontal line — at that point, moving a dollar from one program to another can no longer improve the total.

Graph 4 — 3D benefit landscape. Here we fix the total budget at $1,000,000 and let the Lung and Breast allocations vary freely across the horizontal plane, while the leftover money automatically fills Colorectal and Cervical proportionally to their potential. The height of the surface is the total number of detected cases. Because all four benefit functions are concave, the whole landscape is a smooth dome — there’s a single peak, marked with a red dot, and no isolated local optima to get stuck in. This is a nice visual confirmation that a simple hill-climbing or water-filling approach is guaranteed to find the true best allocation.
Takeaways
This is a small model, but the pattern generalizes directly to real health-policy decisions: whenever benefits from different investments show diminishing returns, the key question is never “how much can I afford for program X” in isolation — it’s “where is a marginal dollar currently doing the most good,” across all competing programs at once. The water-filling algorithm formalizes that intuition and, as a bonus, turns out to be dramatically faster than a generic optimizer when you need to explore many what-if budget scenarios.






















