Backpacking Resupply Optimizer

About & Methodology

What This Tool Does

The Backpacking Resupply Optimizer helps backpackers plan lightweight, nutritionally complete food resupply boxes. Given your trip duration, calorie target, and store type, it selects a basket of shelf-stable, no-cook foods that minimizes total pack weight while meeting dietary reference intake targets — the same trade-off you face when deciding whether to carry almonds or instant mashed potatoes.

Datasets

NHANES / WWEIA — What We Eat in America

The primary source of food composition data. WWEIA is the dietary recall component of the National Health and Nutrition Examination Survey (NHANES), conducted by the USDA and CDC. Each food is assigned an 8-digit FNDDS food code with per-100g nutrient values for ~30 nutrients.

Cycles used: August 2021 – August 2023   ↗ FNDDS databases

USDA FoodAPS — National Household Food Acquisition and Purchase Survey

FoodAPS links actual grocery purchases to FNDDS food codes, providing real-world price data, package sizes, store type flags (grocery, rural, convenience store), and item descriptions from store receipts. This is the source of the "estimated price" values shown in the optimizer.

Coverage: ~4,400 foods with observed purchase prices   ↗ USDA ERS FoodAPS

FNDDS At-a-Glance — Additional Food Descriptions

The 2019–2020 FNDDS "At a Glance" spreadsheet provides secondary food descriptions — brand names, common aliases, and product names — for each food code. These are shown as the gray subtitle text under each food name (e.g. "Girl Scout Tagalong; Little Debbie Nutty Bar").

↗ FNDDS At-a-Glance (XLSX)

Perishability Data — Days Without Refrigeration

A custom-built lookup table mapping each WWEIA food category to the number of days it can safely be carried without refrigeration. Shelf-stable categories (nuts, dried fruit, crackers) get 14+ days; perishable fresh foods get 0 days. When you set the "Days of Food" parameter, the optimizer automatically excludes foods that would spoil before the end of your trip.

Source: WWEIA_Days_Without_Refrigeration.csv (custom, based on USDA food safety guidelines)

Commercial Backpacking Meals (optional)

A table of 166 purchasable portions from Mountain House, Backpacker's Pantry, Peak Refuel and Huel (Hot & Savory meals and powders), with Nutrition Facts, ingredients, price and package weight. Only Mountain House publishes its panel as text. The other three publish an image of the label, so those values were read and coded by generative AI image reading capabilities (per-container column); a sample was hand checked for efficacy.

Labels state values per serving or per package. Each row is scaled to what a hiker carries: with s servings per container, a per-serving value x becomes x·s for a whole pouch, and a per-package value becomes x/s for one serving of a can or tub. Rows are checked with the Atwater estimate, 4P + 4C + 9F, which must land within 20% of labeled calories.

Weights come from the page (Backpacker's Pantry, Peak), the label (Huel), or are derived as servings × grams per serving (Mountain House, dry mix). Mountain House Pro-Paks have no stated weight, so shipping weight is used, which includes packaging and overstates the food.

The Meals slider. At 0 (default) meals are not candidates and the model is unchanged. At share f > 0 each meal becomes a decision variable gm (grams) with per-gram calories km, and one constraint is added: (f − 0.02) · K ≤ Σm km gm ≤ (f + 0.02) · K, where K is the calorie target. Meals are then rounded to whole pouches or servings (nudged one pouch at a time until the share is back inside the band), pinned, and the LP is re-solved for everything else so calories stay on target. The realized share can still land a few points off; the basket card reports it. The category-diversity cap does not apply to meals, since you set their share directly.

Caveats. Meals need hot water, so No-cook keeps only the Huel powders. Mountain House #10 cans are bulk food and are off unless you tick Include #10 cans. Labels list only some nutrients (calories, macros, sodium, calcium, iron, potassium, vitamin D), so every other nutrient counts as zero for a meal, and a very high meal share can be infeasible under the Optimal profile. Dietary filters for meals are inferred by matching ingredient text, not curated tags; check the label. Huel has no price in the table, so it is excluded from the cost objective and the basket total excludes it. Snapshot taken 2026-10-09.  ↗ Scraper and data on GitHub

Optimization Model

Decision Variables

For each candidate food i, the model has two variables:

When diversity is zero, only continuous g_i variables are used and the problem is solved as a linear program (LP) with GLOP. When diversity > 0, binary variables are added and the problem becomes a mixed-integer program (MIP) solved with SCIP. Both solvers are from Google OR-Tools.

Objective Function

The default objective is maximum calorie density — equivalently, minimum total weight for a fixed calorie target:

minimize   Σ g_i

Carrying fewer grams to hit the same calorie goal means a lighter pack. Because calorie density varies by food (nuts ~7 kcal/g, dried fruit ~3 kcal/g, instant meals ~4 kcal/g), the optimizer gravitates toward calorie-dense foods unless constrained by nutrition or diversity.

Other available objectives:

Constraints

Dietary Reference Intakes (DRI)

Nutrient minimums are scaled from DRI values computed for a 70 kg adult per day × number of trip days. The "Nutrition Profile" setting applies a multiplier to each nutrient's minimum:

Prices

All prices are estimates derived from FoodAPS purchase observations, adjusted to 2025 dollars using category-specific food CPI series from the BLS (Consumer Price Index, U.S. City Average, All Urban Consumers). The median observed price per 100g is multiplied by a category-specific inflation factor; a fallback of $0.50/100g × 1.45 (overall food-at-home inflation) is applied where no observed price exists.

Inflation multipliers by food category (2013 average to December 2025):

These multipliers are recomputed each time inflate_prices.py is run against the live BLS public API.

Limitations