Food Processing & Trade¶
Food Processing¶
Overview¶
The food processing module converts raw agricultural products (crops and animal products) into final food products consumed by the population. This captures:
Multi-output processing: Single crops can produce multiple co-products (e.g., wheat → white flour + bran + germ)
Alternative pathways: Different processing options for the same crop (e.g., white flour vs. wholemeal flour from wheat)
Mass balance: Processing losses and byproducts are explicitly tracked
Unit conversion: Conversion from dry matter (DM) to fresh weight as consumed
Processing is represented in the model as PyPSA multi-output links with crop buses as inputs and multiple food buses as outputs. Each pathway creates one link per country, with efficiencies adjusted for food loss and waste factors.
Data Files¶
The two files below, created and distributed for internal food-opt use, define possible food processing pathways and food groups.
- data/foods.csv
Defines crop-to-food processing pathways using a pathway-based format that supports multi-output processing. Each pathway can convert one crop into one or more food products, with conversion factors maintaining mass balance.
Columns:
pathway
: Unique identifier for the processing pathway (e.g.,white_flour
,milled_rice
)crop
: Input crop name (must match config crops list)food
: Output food product namefactor
: Conversion factor (mass of food output per unit mass of crop input)description
: Explanation of the conversion and source reference
Multi-output pathways: Multiple rows with the same pathway name represent co-products from a single processing operation. For example, the
white_flour
pathway produces white flour (0.75), wheat bran (0.20), and wheat germ (0.03) from wheat, with factors summing to ≤ 1.0 to respect mass balance.Alternative pathways: Different pathways for the same crop represent processing alternatives that the model can choose between based on demand and costs. For example, wheat can be processed via
white_flour
orwholemeal_flour
pathways.- data/food_groups.csv
Maps foods to food groups for dietary constraint aggregation and health impact assessment. Each food must be assigned to exactly one food group.
Columns:
food
: Food product name (must match foods produced indata/foods.csv
)group
: Food group identifier (e.g.,grain
,whole_grains
,legumes
,oil
,byproduct
)
Coverage: This file must include all foods that can be produced according to
data/foods.csv
pathways, including byproducts (bran, meal, hulls, etc.). Foods without group assignments will generate warnings and will not contribute to food group constraints or health impact calculations.Food groups: Standard groups include grains, whole_grains, legumes, nuts_seeds, oil, starchy_vegetable, fruits, vegetables, sugar, byproduct, red_meat, poultry, dairy, and eggs. Additional groups can be defined in the config file under
food_groups
.Byproduct handling: Foods assigned to the
byproduct
group (such as wheat bran, rice bran, oat bran, wheat germ, sunflower meal, rapeseed meal, and buckwheat hulls) are excluded from direct human consumption. Instead, these byproducts can be utilized as animal feed (see byproduct-feed-conversion), making them available for livestock production systems.
Food Loss & Waste Adjustments¶
The workflow incorporates food loss (pre-retail) and food waste (retail & household) adjustments when converting crops to foods. Food loss and waste are measured and tracked by the UN under the Sustainable Development Goal 12.3. The FAO is responsible for preparing data on food loss, whereas the UNEP is responsible for preparing data on food waste. Both are available through a UN Statistics Division API.
workflow/scripts/prepare_food_loss_waste.py
retrieves * SDG indicator 12.3.1 data (seriesAG_FLS_PCT
andAG_FOOD_WST_PC
) from the UN Statistics Division API, using ISO-3 area codes. * Food Balance Sheets data (FBS
domain) from FAOSTAT to obtain country-level per-capita food supply.UNSD reports food loss as a percentage. Regional totals (
ALP
product code) are available for M49 regions, while product-level breakdowns (CRL_PUL
,FRT_VGT
,RT_TBR
,ANMPROD
) exist only for the global series. The script therefore: 1. Pulls the latest world loss percentages by product type. 2. Converts them into correction factors by dividing each product share by the globalALP
total (e.g. fruits & vegetables ≈ 25 % / 13 % ≈ 1.9). 3. Applies these factors to each country’s regionalALP
percentage, yielding group-specific loss fractions for the model food groups.Food waste is reported as kilograms per capita per year. To convert this to a fraction of available food supply, the script retrieves the FAOSTAT FBS Grand Total item (kg/capita/year), converts both to grams/day, and computes
waste_fraction = waste_g_day / supply_g_day
.The resulting dataset
processing/{name}/food_loss_waste.csv
lists, for every country and model food group, the derived loss_fraction and waste_fraction.
During build_model
the crop→food conversion links multiply the baseline processing efficiency by (1 - loss_fraction) * (1 - waste_fraction)
for the relevant country-food group pair. Because all factors are multiplicative (dry matter → fresh mass → edible portion → usable food), their ordering does not affect the final efficiency.
Trade¶
Overview¶
The trade module enables inter-regional flows of crops and food products, subject to transport costs.
To avoid creating a complete graph of region-to-region links (entailing \(O(n^2)\) links for \(n\) regions), the model uses a hub-based topology:
Country buses: Each country has local crop/food buses
Hub buses: A small number of hub nodes (configured count)
Hub connections: Regions connect to nearest hubs; hubs connect to each other
This reduces links from \(O(n^2)\) to \(O(n \times h + h^2)\), where \(n\) = regions and \(h\) = hubs.
Configuration¶
trade:
crop_hubs: 20
crop_default_trade_cost_per_km: 1e-2
crop_trade_cost_categories:
bulk_dry_goods:
cost_per_km: 6e-3
crops:
- wheat
- dryland-rice
- wetland-rice
- maize
- soybean
- barley
- oat
- rye
- dry-pea
- chickpea
bulky_fresh:
cost_per_km: 1.4e-2
crops:
- white-potato
- sweet-potato
- yam
- cassava
- sugarbeet
- biomass-sorghum
perishable_high_value:
cost_per_km: 2.2e-2
crops:
- tomato
- carrot
- onion
- cabbage
- banana
- sugarcane
- sunflower
- rapeseed
- groundnut
animal_product_hubs: 20
animal_product_default_trade_cost_per_km: 2.1e-2
animal_product_trade_cost_categories:
chilled_meat:
cost_per_km: 2.8e-2
products:
- cattle meat
- pig meat
non_tradable_crops:
- alfalfa
- biomass-sorghum
- silage-maize
non_tradable_animal_products: []
Trade Cost Categories¶
Transport costs differentiate by commodity handling requirements:
Bulk dry goods: Cereals, legumes in containers/bulk carriers
Bulky fresh: Potatoes, cassava, sugar beets
Perishable high-value: Fruits, vegetables, sugarcane requiring refrigeration
Chilled meat: Temperature-controlled meat transport
Hub Location¶
Hub positions are determined by k-means clustering on region centroids:
Compute population-weighted centroid for each region
Run k-means with k = configured hub count
Assign each region to nearest hub
Create hub-hub distance matrix for hub-to-hub transport
This ensures hubs are spatially distributed to minimize total transport distance.

Hub-based trade network showing trade hubs (green circles) and trade links: country-to-hub links (thin) and hub-to-hub links (thick).¶
Non-Tradable Commodities¶
Certain products are designated non-tradable:
Fodder crops (alfalfa, biomass sorghum): Too bulky/low-value to transport
Perishables (optional): Can restrict local consumption of fragile goods
Non-tradable crops must be consumed (as food or feed) within their production region.
Model Implementation¶
Trade links are created in workflow/scripts/build_model.py
:
# Pseudocode
for crop in tradable_crops:
for region in regions:
hub = nearest_hub(region)
n.add("Link",
f"trade_{crop}_{region}_to_{hub}",
bus0=f"crop_{crop}_{region}",
bus1=f"crop_{crop}_hub{hub}",
p_nom=inf, # No capacity limit
marginal_cost=distance * cost_per_km)
for hub_i, hub_j in hub_pairs:
n.add("Link",
f"trade_{crop}_hub{hub_i}_to_hub{hub_j}",
bus0=f"crop_{crop}_hub{hub_i}",
bus1=f"crop_{crop}_hub{hub_j}",
p_nom=inf,
marginal_cost=hub_distance * cost_per_km)
Similar structure for animal products.