Allocate a budget to maximize return in Python

Resource allocation — splitting a fixed budget, headcount, or capacity across competing options to get the most out of it — is one of the most common optimization problems in day-to-day code: allocate marketing spend across channels, capital across projects, compute across jobs. With quicopt it is a short linear program (LP) in Python.

The naive approach

Without the vocabulary, the instinct is to grid-search the splits or greedily pour money into the highest-return option:

naive.py
# Greedy: fill the highest-return option first, then the next...
# — fine with a single budget, but breaks once the options are coupled.
# A grid search over splits is exponential in the number of options.

Filling the highest return-per-euro options in order works when a single budget is the only limit — but it breaks as soon as the options are coupled: a second scarce resource (limited team time), per-group budgets, or minimum commitments. A grid search explodes with the number of options. The problem is continuous and linear — so model it and solve it exactly.

Model it as an LP

Each option gets a continuous variable spend[i] bounded by its cap. One budget constraint limits the total, and the objective maximizes total return:

resource_allocation.py
from ortools.math_opt.python import mathopt
from quicopt import Client

# Allocate a fixed budget across channels to maximize return; each channel has a
# per-euro return and a spend cap.
ret  = [0.12, 0.10, 0.15, 0.08]   # return per euro spent
cap  = [40.0, 50.0, 30.0, 60.0]   # max spend per channel
budget = 100.0
N = len(ret)

model = mathopt.Model(name="resource_allocation")
x = [model.add_variable(lb=0.0, ub=cap[i], name=f"spend_{i}") for i in range(N)]
model.add_linear_constraint(sum(x) <= budget)
model.maximize(sum(ret[i] * x[i] for i in range(N)))

client = Client("https://try.quicoptapi.pgi.fz-juelich.de")
print(client.solve(model).display)
$ python resource_allocation.py
├── status:     optimal
├── feasible:   true
├── objective:  12.3
├── x:          spend_0=40, spend_1=30, spend_2=30, spend_3=0  (4 variables)
└── solve_time: 0.0037 s

What you get

status: optimal means the split is proven best — return 12.3 from spending 40 / 30 / 30 / 0, found in under four milliseconds. With a single budget this happens to line up with filling the best return-per-euro options in order; the moment you add coupling — a second scarce resource, per-group budgets, or minimum spends — that shortcut breaks and the LP is what still returns the proven-best mix.

Because it is an LP, the same model scales from four options to four thousand — you change the data, not the method.

Next

Reference: G. B. Dantzig, Linear Programming and Extensions, Princeton University Press, 1963.

A bigger allocation problem — or a different one?

Tell us what you're optimizing. We'll help you model it and point you at the right approach.

Talk to us →

Frequently asked questions

Isn't this just spending everything on the highest-return option?

With a single budget, filling the highest return-per-euro options in order works. But add a second scarce resource, per-group budgets, or minimum spends and the choices become coupled — the best split is no longer obvious, which is exactly what the LP solves.

Can I add per-group budgets, minimum spends, or other rules?

Yes. They are just more linear constraints on the same variables; the model and the solve call stay the same.

Is quicopt free to use?

Yes — pip install quicopt and your first call sets up a free key, no license.