Skip to content

Optimization MMA 861 · Smith School of Business, Queen’s University

Priced hospital menu variety at +37% food cost with a MILP

Hospital menus must be cheap, clinically safe and varied. I led a team that built a MILP for five clinical diets; limiting ingredient repeats raised 3-day food cost 37% ($6.81 to $9.34), giving managers a price for variety.

8 min read

Result
With no ingredient repeated within a day or on more than 2 of 3 days, average 3-day food cost rose from $6.81 to $9.34 per patient (+37%) across five clinical diets, with every nutrient bound met.
Impact
Estimated $0.84 per patient-day, about $123,000 a year for a 400-patient hospital, assuming an even mix of the five diets and the synthetic wholesale prices in the model.
My role
Team lead: led the formulation, constraint design and sensitivity analysis · Team project · team lead
Context
MMA 861 · Smith School of Business, Queen’s University
Timeframe
2026
Tools
PythonMixed-Integer ProgrammingSciPy (HiGHS)Linear ProgrammingSensitivity AnalysisPlotly
+18% to +46%
cost of variety by diet, against each diet’s cost-only menu
17 → 24
average distinct ingredients per 3-day menu, out of 41
$0.29–$0.41
shadow price of each “one fruit per meal” rule, the costliest constraint in every diet
Every diet costs more once ingredient repeats are limited
3-day food cost per patient by clinical diet and repeat rule Grouped bar chart. Five clinical diets, three bars each: the cost-only menu, a menu with no same-day repeats, and a menu that also caps each ingredient at 2 of 3 days. Every diet costs 18 to 46 percent more under the repeat rules. Asterisks mark the 8 of 15 runs that stopped at the 180-second time limit without a proof of optimality. $0 $3 $6 $9 $12 Normal Male, cost only: $7.05 (proven optimal) Normal Male, no same-day repeats: $9.93 (stopped at time limit) Normal Male, + max 2 of 3 days: $9.81 (stopped at time limit) * * Normal Male Normal Female, cost only: $7.31 (stopped at time limit) Normal Female, no same-day repeats: $10.67 (stopped at time limit) Normal Female, + max 2 of 3 days: $10.67 (stopped at time limit) * * * Normal Female Diabetic, cost only: $7.88 (stopped at time limit) Diabetic, no same-day repeats: $9.57 (stopped at time limit) Diabetic, + max 2 of 3 days: $9.30 (stopped at time limit) * * * Diabetic High Cholesterol, cost only: $5.75 (proven optimal) High Cholesterol, no same-day repeats: $8.28 (proven optimal) High Cholesterol, + max 2 of 3 days: $8.28 (proven optimal) High Cholesterol DASH, cost only: $6.06 (proven optimal) DASH, no same-day repeats: $8.63 (proven optimal) DASH, + max 2 of 3 days: $8.63 (proven optimal) DASH Cost only No same-day repeats + max 2 of 3 days * run stopped at the 180 s limit: best menu found, not proven optimal
3-day food cost per patient, three MILP runs per diet with identical constraints except the repeat rules. The repeat rules add 18% to 46% depending on the diet, and 37% on average with the 2-of-3-days cap.

A hospital food-service manager has to keep ingredient cost down, keep every patient inside a clinical nutrition range, and keep menus varied enough that patients eat. A patient who stops eating is a clinical problem. Manual planning usually gives up one of the three, and the manager has no number for what the trade costs. Variety is the one that gets cut, because it sounds like a preference while cost and nutrition sound like requirements. We set out to answer one question for that manager: what does a varied menu cost per patient, compared with the cheapest menu that is still clinically compliant? Success meant a dollar figure per diet, from menus that pass every nutrient bound.

The model runs on public nutrition data and synthetic prices

  • 41 ingredients in five food groups (10 fruit, 6 vegetable, 9 grain, 10 protein, 6 fat), each with 13 nutrients per gram from USDA food data.
  • 5 clinical diets: normal male, normal female, diabetic, high cholesterol and DASH (high blood pressure). Daily bounds follow Health Canada guidance, scaled to a 400-patient baseline. Per-meal calorie windows use a 35/40/25 breakfast/lunch/dinner split from Women’s College Hospital estimates, and the diabetic diet adds a 45–60 g carbohydrate window at every meal.
  • 17 culinary rules that block pairings such as oatmeal with beef or granola with salmon.
  • Prices are synthetic, based on wholesale averages, so the model could be shared without a supplier agreement. Every dollar figure on this page depends on them.

One 3-day block has 9 meals, 261 eligible ingredient-meal pairs and 686 decision variables.

Four modelling choices turned variety into a dollar figure

Yes/no menu rules forced a mixed-integer model

The model makes two kinds of decision at once: whether ingredient i is on the plate at meal j (binary), and how many grams (continuous). A linking constraint ties them together, so an unselected ingredient gets exactly zero grams and a selected one lands in a realistic portion range for that meal. A pure linear program cannot express “exactly one fruit per meal” or “no oatmeal with beef”, so we rejected it. The original proposal planned Excel Solver, which we dropped because the model outgrew its variable limits. We built it in Python on SciPy’s HiGHS MILP solver.

Each of the 11 constraint families closes a loophole the solver found

Without per-meal calorie windows, the solver put almost all calories into one meal. Without a diabetic carb window per meal, it stacked carbs into one spike while meeting the daily total. Without composition rules, breakfast was nutritionally valid and unrecognisable. Without the culinary rules, it served granola with salmon because the pairing was cheap. Each family in the appendix table exists because a run produced something a dietitian would reject.

We made variety a hard rule so the answer comes out in dollars

We could have added a variety penalty to the objective. That would need an exchange rate between dollars and patient satisfaction, and any rate we chose would decide the answer. Hard rules avoid that: we solved every diet three times with identical constraints except the repeat rules, and the cost difference between runs is the price of variety. The three runs are: cost only (repeats allowed), no ingredient twice in one day, and that plus no ingredient on more than 2 of the 3 days.

The one-fruit-per-meal rule carries the highest shadow price in every diet

Integer programs do not produce usable dual values, so we solved the LP relaxation of the strictest model, with every binary relaxed to the range 0 to 1, and read the shadow prices. In all five diets, the nine “exactly one fruit per meal” constraints were the most expensive, at $0.29 to $0.41 each at the margin. No nutrient bound made any diet’s top 10. The relaxed costs ($6.08 to $6.83) sit below the integer menus, as a lower bound should. Among nutrients, the binding pressure shows up in the compliance check below instead: in the diabetic menu, fibre sits on its floor and potassium almost on its ceiling.

The diabetic menu sits on its fibre floor and near its potassium ceiling
Nutrient compliance, diabetic diet, strictest repeat rule Ten nutrients shown as a bar from the clinical minimum to the actual 3-day average, within the allowed range. Fibre sits just above its minimum, potassium just below its maximum, and sodium well below its limit. Protein Protein: 94.4g (range 90-132g), 10% of allowed range 94.4g [90–132] Carbs Carbs: 176.2g (range 130-240g), 42% of allowed range 176.2g [130–240] Fat Fat: 62.4g (range 60-90g), 8% of allowed range 62.4g [60–90] Sat. Fat Sat. Fat: 12.3g (range 0-18g), 68% of allowed range 12.3g [0–18] Fibre Fibre: 30.2g (range 30-42g), 2% of allowed range 30.2g [30–42] Calcium Calcium: 861mg (range 800-1000mg), 30% of allowed range 861mg [800–1000] Sodium Sodium: 623mg (range 0-2300mg), 27% of allowed range 623mg [0–2300] Potassium Potassium: 3116mg (range 2080-3120mg), 100% of allowed range 3116mg [2080–3120] Iron Iron: 10.8mg (range 8-45mg), 8% of allowed range 10.8mg [8–45] Vit. D Vit. D: 8.3mcg (range 5-33mcg), 12% of allowed range 8.3mcg [5–33] Min 50% Max
Diabetic diet, strictest repeat rule, 3-day averages from the saved results workbook. Each bar runs from the clinical minimum to the actual value. Fibre averages 30.2 g against a 30 g floor and potassium 3,116 mg against a 3,120 mg ceiling, so those two bounds leave the least room when ingredient prices change.

Limiting repeats raised food cost 37%, and every menu met its bounds

Diet Cost only No same-day repeats + max 2 of 3 days Change vs cost only
Normal male $7.05 $9.93* $9.81* +39%
Normal female $7.31* $10.67* $10.67* +46%
Diabetic $7.88* $9.57* $9.30* +18%
High cholesterol $5.75 $8.28 $8.28 +44%
DASH $6.06 $8.63 $8.63 +42%
Average $6.81 $9.42 $9.34 +37%

* stopped at the 180-second time limit with a feasible menu that is not proven optimal.

  • Validation. We checked every menu against every daily nutrient bound for all three days, with a 0.5-unit tolerance. No run violated a bound.
  • Optimality. 7 of 15 runs were proven optimal; 8 stopped at the time limit. The result holds on the clean runs: high cholesterol (+44%) and DASH (+42%) were proven optimal under all three rules.
  • An anomaly we can explain. For the normal male and diabetic diets, the stricter rule came out cheaper than the looser one ($9.81 vs $9.93, $9.30 vs $9.57). A stricter rule cannot lower the true optimum, so the looser runs stopped before finding their best menu. Their true cost of variety may be somewhat lower than shown.
  • Where the cost comes from. In both diets where every run was proven optimal, the 2-of-3-days cap added $0. The whole increase came from banning same-day repeats.
  • Variety gained. Distinct ingredients per 3-day menu rose from 17 to 24 on average, and same-day repeats fell from 9–13 per menu to zero.

Recommendation: budget for no same-day repeats, then work on fruit cost

A food-service manager should treat “no ingredient twice in one day” as the default and budget about $0.84 per patient-day for it (estimated, synthetic prices). The 2-of-3-days cap can be added on top: in the cleanly solved diets it cost nothing. The cheapest saving is in fruit, which carries the highest shadow price in every diet. Negotiating fruit prices or approving cheaper fruits for the menu would cut cost more than relaxing any nutrient bound. After rollout, the measure to watch is plate waste and intake by diet, because that is what variety is meant to buy.

Limitations and what we’d do next

  • 8 of 15 runs are not proven optimal, and we did not record their optimality gaps. Next: rerun with a longer limit and report the gap for each run, starting with the normal male and diabetic 3-day runs that produced the anomaly.
  • Prices are synthetic. The 37% and the $123,000 estimate move with real supplier prices. Next: rerun on one hospital’s actual price list.
  • Diets are solved one at a time. A real kitchen feeds every diet from one ingredient pool. Next: a joint model with shared purchasing across diets.
  • Shadow prices come from a fully relaxed LP, which can pick fractional menus. Next: fix the chosen menu and re-solve the portions to get dual values for the menu actually served.
  • The block is 3 days. A production planner needs a full week or a rotating cycle.

Team and credits

MMA 861 team project. I led the team, and led the formulation, the constraint design and the sensitivity analysis. The model, notebook and write-up were built together.

Technical appendix

Formulation. Decision variables: x[i,j] binary (ingredient i at meal j), q[i,j] continuous grams, a[i,d] binary (ingredient i used on day d) and r[i] binary (used on more than one day). Objective: minimise Σ price per gram × q[i,j]. The linking constraint:

qMin(i,j) · x(i,j)  ≤  q(i,j)  ≤  qMax(i,j) · x(i,j)

Constraint families.

Constraint The loophole it closes
C1 Daily nutrient bounds for 10 nutrients (vitamin A and sugar monitored only) “Cheapest” would mean nutritionally void
C2 Per-meal calorie windows, 35/40/25 split All calories in one meal
C3 Diabetic carbs 45–60 g per meal One carb spike that still meets the daily total
C4 Exactly one fruit per meal; 1–4 non-fruit items A plate of one cheap ingredient
C5 17 culinary incompatibilities (13 all meals, 4 breakfast only) Granola with salmon
C6 Breakfast: ≥1 protein, exactly 1 grain, exactly 1 fat Valid but unrecognisable breakfasts
C7 Lunch and dinner: ≥1 protein, ≥1 vegetable, 1–2 grains, plate ratio Plates with no structure
C8 Meal-specific portion bounds 5 g of chicken or 500 g of rice
C9 Day-presence linking Makes the rotation rules expressible
C10 Each ingredient at most once per day Same ingredient at lunch and dinner
C11 Each ingredient on at most 2 (or 3) of 3 days The same cheap items every day

Solver settings. SciPy milp (HiGHS), 180-second time limit per run, 15 runs (5 diets × 3 rules). Run status, time and distinct ingredients:

Diet Cost only No same-day repeats + max 2 of 3 days
Normal male optimal, 96.7 s, 16 time limit, 24 time limit, 24
Normal female time limit, 17 time limit, 26 time limit, 26
Diabetic time limit, 21 time limit, 24 time limit, 24
High cholesterol optimal, 18.9 s, 16 optimal, 64.1 s, 23 optimal, 77.3 s, 23
DASH optimal, 23.8 s, 17 optimal, 77.8 s, 23 optimal, 89.2 s, 23

LP relaxation (strictest rule). Relaxed cost: normal male $6.83, normal female $6.77, diabetic $6.80, high cholesterol $6.08, DASH $6.27. Largest shadow prices (dollars per unit of the constraint): the nine one-fruit-per-meal rules in every diet, from −$0.29 to −$0.41. The tenth is “≥1 protein at day-1 lunch” (−$0.31) for normal male and a banana once-per-day cap (−$0.22 to −$0.26) for the other four diets.

What didn’t work. Excel Solver (variable limits). Reading duals straight from the MILP (not defined with integer variables). Treating the compliance chart from one saved run as stable: a second run of the same diabetic model put potassium at 2,974 mg, so only the fibre floor binds in both runs.