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:
# 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:
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)
├── 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
- The problem class behind this: Linear programming (LP)
- A runnable model for every supported class: Examples
- Set up the client and solve your first model: Getting started
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.
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.