Configuration

Overview

The food-opt model is configuration-driven: all scenario parameters, crop selections, constraints, and solver options are defined in YAML configuration files under config/. This allows exploring different scenarios without modifying code.

The default configuration is config/default.yaml, structured into thematic sections.

Custom configuration files

Instead of modifying the default configuration file, it is recommended to explore individual scenarios by creating named configuration files, overriding specific parts of the default configuration. Such a named configuration file must contain at the minimum a name. An example could be something like the following:

# config/my_scenario.yaml
name: "my_scenario"           # Scenario name → results/my_scenario/
planning_horizon: 2040        # Override the default 2030 horizon
primary:
  land:
    regional_limit: 0.6       # Tighten land availability
emissions:
  ghg_price: 250              # Raise the carbon price above the default

Any keys omitted in your custom file fall back to the defaults shown in the sections below, so you can keep overrides concise.

Results are saved under results/{name}/, allowing multiple scenarios coming from different configuration files to coexist.

To build and solve the model based on the above example configuration, you would run the following:

tools/smk -j4 --configfile config/my_scenario.yaml

Configuration sections

Planning Horizon

planning_horizon: 2030

Matches UN WPP population year and GAEZ climate period.

Download Options

downloads:
  show_progress: true

Crop Selection

crops:
# Core cereals
- wheat
- dryland-rice
- wetland-rice
- maize
- barley
- oat
- rye
- sorghum
- buckwheat
- foxtail-millet
- pearl-millet
# Legumes/pulses
- soybean
- dry-pea
- chickpea
- cowpea
- gram
- phaseolus-bean
- pigeonpea
# Roots and tubers
- white-potato
- sweet-potato
- cassava
- yam
# Vegetables
- tomato
- carrot
- onion
- cabbage
# Fruits
- banana
- citrus
- coconut
# Oil crops
- sunflower
- rapeseed
- groundnut
- sesame
- oil-palm
- olive
# Sugar crops
- sugarcane
- sugarbeet
# Fodder
- alfalfa
- biomass-sorghum
# Note: mango and taro excluded - missing RES02 (growing season) data for GFDL-ESM4

See Crop Production for full list. Add/remove crops to explore specialized vs. diversified production systems.

Country Coverage

countries:
# - ABW  # No level-1 GADM data
- AFG
- AGO
# - AIA  # No regions (microstate)
# - ALA  # No population
- ALB
# - AND  # excluded: microstate
- ARE
- ARG
- ARM
- ASM
# - ATA  # No level-1 GADM data
# - ATF  # No population
- ATG
- AUS
- AUT
- AZE
- BDI
- BEL
- BEN
# - BES  # excluded: small overseas territory
- BFA
- BGD
- BGR
# - BHR  # excluded: desert city-state
- BHS
- BIH
# - BLM  # No regions (microstate)
- BLR
- BLZ
# - BMU  # No regions (microstate)
- BOL
- BRA
- BRB
- BRN
- BTN
# - BVT  # No level-1 GADM data
- BWA
- CAF
- CAN
# - CCK  # No level-1 GADM data
- CHE
- CHL
- CHN
- CIV
- CMR
- COD
- COG
# - COK  # excluded: small island territory
- COL
- COM
- CPV
- CRI
- CUB
# - CUW  # No level-1 GADM data
# - CXR  # No level-1 GADM data
# - CYM  # excluded: small overseas territory
- CYP
- CZE
- DEU
- DJI
# - DMA  # excluded: small island state
- DNK
- DOM
- DZA
- ECU
- EGY
- ERI
# - ESH  # excluded: sparse desert territory
- ESP
- EST
- ETH
- FIN
- FJI
# - FLK  # No level-1 GADM data
- FRA
# - FRO  # excluded: small island territory
# - FSM  # excluded: small island state
- GAB
- GBR
- GEO
# - GGY  # Too small
- GHA
# - GIB  # No level-1 GADM data
- GIN
# - GLP  # excluded: overseas department
- GMB
- GNB
- GNQ
- GRC
- GRD
# - GRL  # excluded: ice-dominated
- GTM
- GUF
# - GUM  # excluded: small island territory
- GUY
# - HKG  # No level-1 GADM data
# - HMD  # No level-1 GADM data
- HND
- HRV
- HTI
- HUN
- IDN
# - IMN  # excluded: small island territory
- IND
# - IOT  # No level-1 GADM data
- IRL
- IRN
- IRQ
- ISL
- ISR
- ITA
- JAM
# - JEY  # No regions (microstate)
- JOR
- JPN
- KAZ
- KEN
- KGZ
- KHM
# - KIR  # No level-1 GADM data
# - KNA  # excluded: small island state
- KOR
# - KWT  # excluded: desert city-state
- LAO
- LBN
- LBR
- LBY
# - LCA  # excluded: small island state
# - LIE  # excluded: microstate
- LKA
- LSO
- LTU
- LUX
- LVA
# - MAC  # No level-1 GADM data
# - MAF  # No level-1 GADM data
- MAR
# - MCO  # No level-1 GADM data
- MDA
- MDG
# - MDV  # No level-1 GADM data
- MEX
# - MHL  # No regions (microstate)
- MKD
- MLI
- MLT
- MMR
- MNE
- MNG
# - MNP  # excluded: small island territory
- MOZ
- MRT
# - MSR  # excluded: small island territory
# - MTQ  # excluded: overseas department
- MUS
- MWI
- MYS
# - MYT  # excluded: overseas department
- NAM
# - NCL  # excluded: overseas territory
- NER
# - NFK  # No level-1 GADM data
- NGA
- NIC
# - NIU  # No level-1 GADM data
- NLD
- NOR
- NPL
# - NRU  # No regions (microstate)
- NZL
- OMN
- PAK
- PAN
# - PCN  # No level-1 GADM data
- PER
- PHL
# - PLW  # excluded: small island state
- PNG
- POL
- PRI
# - PRK  # excluded: no health data available for North Korea
- PRT
- PRY
- PSE
# - PYF  # excluded: overseas territory
# - QAT  # excluded: desert city-state
# - REU  # excluded: overseas department
- ROU
- RUS
- RWA
- SAU
- SDN
- SEN
# - SGP  # excluded: desert city-state (urban)
# - SGS  # No level-1 GADM data
# - SHN  # excluded: small island territory
# - SJM  # No population
- SLB
- SLE
- SLV
# - SMR  # No regions (microstate)
- SOM
# - SPM  # excluded: small island territory
- SRB
- SSD
- STP
- SUR
- SVK
- SVN
- SWE
- SWZ
# - SXM  # No level-1 GADM data
# - SYC  # excluded: small island state
- SYR
# - TCA  # excluded: small island territory
- TCD
- TGO
- THA
- TJK
# - TKL  # No regions (microstate)
- TKM
- TLS
# - TON  # excluded: small island state
- TTO
- TUN
- TUR
# - TUV  # No regions (microstate)
- TWN
- TZA
- UGA
- UKR
# - UMI  # No population
- URY
- USA
- UZB
# - VAT  # No level-1 GADM data
# - VCT  # excluded: small island state
- VEN
# - VGB  # excluded: small island territory
# - VIR  # excluded: small island territory
- VNM
- VUT
# - WLF  # excluded: overseas territory
# - WSM  # excluded: small island state
- YEM
- ZAF
- ZMB
- ZWE

Include countries/territories to model; exclude to reduce problem size. Microstate and countries missing essential data are commented out.

Spatial Aggregation

Controls regional resolution and land classification.

aggregation:
  regions:
    type: "cluster"
    target_count: 400
    allow_cross_border: false
    method: "kmeans"
  simplify_tolerance_km: 5
  simplify_min_area_km: 25
  resource_class_quantiles: [0.25, 0.5, 0.75]
  # Crop land-use limitation source used when aggregating yields by region/resource class.
  # - "suitability": limit area using GAEZ suitability rasters per water supply (irrigated/rainfed)
  # - "irrigated": limit area using the irrigated cropland share (for irrigated) and its complement (for rainfed)
  land_limit_dataset: "irrigated"
Trade-offs:
  • More regions → higher spatial resolution, longer solve time

  • Fewer resource classes → faster solving, less yield heterogeneity

Primary Resource Constraints

Limits on land, water, and fertilizer availability.

primary:
  land:
    regional_limit: 0.7 # fraction of each region's potential cropland that is made available.
  fertilizer:
    limit: 2e11 # kg (200 Mt total NPK - realistic global scale; converted to Mt inside the model)
    # High-input agriculture N application rates (percentile of global FUBC data)
    n_percentile: 80  # Use 80th percentile for high-input systems (range: 0-100)
    synthetic_n2o_factor: 0.010 # kg N2O-N per kg N input (IPCC 2019 Refinement, Table 11.1 aggregated default)

GAEZ Data Parameters

Configures which GAEZ v5 climate scenario and input level to use.

data:
  gaez:
    # GAEZ v5 parameters
    # Note: RES05 (yields/suitability) has ENSEMBLE, but RES02 (growing season) only has individual GCMs
    climate_model: "GFDL-ESM4" # Specific GCMs: "GFDL-ESM4", "IPSL-CM6A-LR", "MPI-ESM1-2-HR", "MRI-ESM2-0", "UKESM1-0-LL"
    climate_model_ensemble: "ENSEMBLE" # Multi-model mean (only available for RES05, not RES02)
    period: "FP2140" # Future: "FP2140" (2021-2040), "FP4160" (2041-2060), "FP6180" (2061-2080), "FP8100" (2081-2100); Historical: "HP0120" (2001-2020), "HP8100" (1981-2000)
    scenario: "SSP126" # "SSP126" (low emissions), "SSP370" (medium, ~RCP4.5), "SSP585" (high), "HIST" (historical)
    input_level: "H" # "H" (High), "L" (Low)
    water_supply: "R" # "I" (irrigated), "R" (rainfed)
    # Variable codes for GAEZ v5
    yield_var: "RES05-YCX" # Average attainable yield, current cropland
    water_requirement_var: "RES05-WDC" # Water deficit/net irrigation requirement during crop cycle, current cropland
    suitability_var: "RES05-SX1" # Share of grid cell assessed as VS or S (very suitable or suitable)
  usda:
    api_key: "NZESBYEsg9Utlh3OmIsHu7pgEI2AD3jN76SxgCq6"
    retrieve_nutrition: true  # Set to true to fetch nutrition data from USDA instead of using the provided data
    # Nutrient mapping: internal name -> USDA FoodData Central name
    # USDA names must match nutrient names in FoodData Central exactly
    nutrients:
      protein: "Protein"
      carb: "Carbohydrate, by difference"
      fat: "Total lipid (fat)"
      kcal: "Energy"
  land_cover:
    year: "2022"
    version: "v2_1_1"
  soilgrids:
    target_resolution_m: 10000  # Target resolution in meters (10000m = 10km)
Scenarios:
  • SSP126: Strong mitigation (1.5-2°C warming)

  • SSP370: Moderate emissions (~3°C)

  • SSP585: High emissions (~4-5°C)

Input Levels:
  • H: Modern agriculture (fertilizer, irrigation, pest control)

  • L: Subsistence farming (minimal external inputs)

Irrigation

irrigation:
  # Which model crops are allowed to have irrigated production.
  # In GAEZ v5, all crops have both irrigated (HILM/LILM) and rainfed (HRLM/LRLM) data available.
  # List specific crops here if you want to restrict irrigation, or use "all" for all crops.
  irrigated_crops: "all"

Restrict irrigation to water-scarce scenarios or explore rainfed-only production.

Macronutrients

macronutrients:
  carb:
    min: 250     # g/person/day
  protein:
    min: 50      # g/person/day
  fat:
    min: 50      # g/person/day
  kcal:
    equal: 2400  # kcal/person/day (source: EAT/Lancet 2025)

Use min, max, or equal constraints.

Food Groups

food_groups:
  whole_grains:
    min: 0
  grain:
    min: 0
  fruits:
    min: 0
  vegetables:
    min: 0
  legumes:
    min: 0
  nuts_seeds:
    min: 0
  starchy_vegetable:
    min: 0
  oil:
    min: 0
  red_meat:
    min: 0
  dairy:
    min: 0

Each food group may specify min_per_person_per_day, max_per_person_per_day, and equal_per_person_per_day. The defaults leave minima at zero so food group constraints stay inactive; tighten minima or maxima to guide intakes, or use the equal field for equality targets.

Diet Controls

diet:
  enforce_gdd_baseline: false
  baseline_age: "All ages"
  baseline_reference_year: null  # falls back to health.reference_year when null

Enable enforce_gdd_baseline to force the optimization to match baseline consumption from the processed GDD file. Override baseline_age or baseline_reference_year if you pre-process alternative cohorts or years.

Animal Products

animal_products:
  include:
  - cattle meat
  - pig meat
  - chicken meat
  - dairy
  - eggs
  residue_crops:
  - banana
  - barley
  - chickpea
  - cowpea
  - dry-pea
  - dryland-rice
  - foxtail-millet
  - gram
  - maize
  - oat
  - pearl-millet
  - phaseolus-bean
  - pigeonpea
  - rye
  - sorghum
  - sugarcane
  - wetland-rice
  - wheat

grazing:
  enabled: true

Disable grazing to force intensive feed-based systems.

Trade 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: []

Increase trade costs to explore localized food systems; decrease for globalized trade.

Emissions Pricing

emissions:
  ghg_price: 200 # USD_2024/tCO2-eq (emissions stored in MtCO2-eq internally)
  ch4_to_co2_factor: 27.0 # IPCC AR6 GWP100 (WG1, Chapter 7, Table 7.15; https://www.ipcc.ch/report/ar6/wg1/chapter/chapter-7/)
  n2o_to_co2_factor: 273.0 # IPCC AR6 GWP100 (WG1, Chapter 7, Table 7.15; https://www.ipcc.ch/report/ar6/wg1/chapter/chapter-7/)

Land Use Change

luc:
  horizon_years: 25
  managed_flux_mode: "zero"
  forest_fraction_threshold: 0.2  # Minimum forest fraction (0-1) to apply regrowth sequestration
  spared_land_agb_threshold_tc_per_ha: 20.0  # Max AGB (tC/ha) for spared land eligibility

Controls how land use change emissions and carbon sequestration are modeled over the planning horizon.

Parameters:
  • horizon_years: Time horizon (years) for amortizing land use change emissions

  • managed_flux_mode: How to treat emissions from existing managed land ("zero" assumes no net flux from current agricultural land)

  • forest_fraction_threshold: Minimum forest cover fraction (0-1) required for a grid cell to be eligible for regrowth sequestration when land is spared

  • spared_land_agb_threshold_tc_per_ha: Maximum above-ground biomass (tonnes C per hectare) for spared land to be eligible for regrowth sequestration

Health Configuration

health:
  region_clusters: 30
  reference_year: 2018
  intake_grid_step: 20   # Intake resolution in g/person/day
  log_rr_points: 10
  omega3_per_100g_fish: 1.5
  value_per_yll: 150000  # USD per year of life lost
  # Dietary risk factors to consider (must match GDD data items)
  risk_factors:
  - fruits
  - vegetables
  - nuts_seeds
  - legumes
  - fish
  - red_meat
  - prc_meat
  - whole_grains
  # Health outcomes/causes to consider (must be present in IHME GBD data and relative risks)
  causes:
  - CHD              # Coronary/Ischemic Heart Disease
  - Stroke           # Stroke (all types)
  - T2DM             # Type 2 Diabetes Mellitus
  - CRC              # Colorectal Cancer
  # Theoretical minimum risk exposure levels (TMREL) from GBD Study 2021
  # Source: Brauer et al. (2024), Global Burden of Disease Study 2021
  # Values represent optimal intake levels where health risk is minimized
  # Reference: https://doi.org/10.1016/S0140-6736(24)00933-4
  tmrel_g_per_day:
    fruits: 345         # TMREL: 340-350 g/day (midpoint)
    vegetables: 339     # TMREL: 306-372 g/day (midpoint)
    whole_grains: 185   # TMREL: 160-210 g/day (midpoint)
    nuts_seeds: 21.5    # TMREL: 19-24 g/day (midpoint)
    legumes: 105        # TMREL: 100-110 g/day (midpoint)
    fish: 37.7          # TMREL: 470-660 mg/day omega-3 (midpoint 565 mg, converted using omega3_per_100g_fish)
    red_meat: 0         # TMREL: 0-200 g/day (using conservative lower bound)
    prc_meat: 0         # TMREL: 0 g/day (any intake increases risk)

Reduce region_clusters or log_rr_points to speed up solving.

Solver Configuration

solving:
  solver: highs
  # solver: gurobi
  options_gurobi:
    LogToConsole: 0
    OutputFlag: 1
    Method: 2
    MIPGap: 0.001  # target 0.1% relative optimality gap
  options_highs:
    solver: "ipm"
    mip_rel_gap: 0.001  # align relative gap with gurobi setting
Solver choice:
  • HiGHS: Open-source, fast, good for most problems

  • Gurobi: Commercial, often faster for very large problems, requires license (free for academic users)

Plotting Configuration

plotting:
  colors:
    crops:
      wheat: "#C58E2D"
      'dryland-rice': "#E0B341"
      'wetland-rice': "#F7E29E"
      maize: "#F1C232"
      barley: "#B68D23"
      oat: "#D4B483"
      rye: "#A67C52"
      sorghum: "#A0522D"
      buckwheat: "#8B5A2B"
      'foxtail-millet': "#E3C878"
      'pearl-millet': "#D9A441"
      soybean: "#7B4F2A"
      'dry-pea': "#B9925B"
      chickpea: "#D7B377"
      cowpea: "#8C5C38"
      gram: "#A47038"
      'phaseolus-bean': "#6E3B1E"
      pigeonpea: "#9C6B3E"
      'white-potato': "#8FB98B"
      'sweet-potato': "#CE7B3A"
      cassava: "#6E8B3D"
      yam: "#4F6F2C"
      tomato: "#C0392B"
      carrot: "#E67E22"
      onion: "#D35400"
      cabbage: "#27AE60"
      banana: "#F7DC6F"
      citrus: "#F39C12"
      coconut: "#8E735B"
      sunflower: "#F1C40F"
      rapeseed: "#F5B041"
      groundnut: "#A8683C"
      sesame: "#C97A2B"
      'oil-palm': "#A04000"
      olive: "#6E7D57"
      sugarcane: "#9B59B6"
      sugarbeet: "#AF7AC5"
      alfalfa: "#1ABC9C"
      'biomass-sorghum': "#16A085"
      grassland: "#7FB77E"
    food_groups:
      whole_grains: "#8C564B"
      grain: "#C49C94"
      fruits: "#E15759"
      vegetables: "#59A14F"
      legumes: "#B07AA1"
      nuts_seeds: "#AA7C51"
      starchy_vegetable: "#F28E2C"
      oil: "#FFBE7D"
      red_meat: "#D62728"
      poultry: "#FF9896"
      dairy: "#9EDAE5"
      eggs: "#FFE377"

  fallback_cmaps:
    crops: "Set3"

Customize visualization colors for publication-quality plots. The colors.food_groups palette is applied consistently across all food-group charts and maps; extend it if you add new groups to data/food_groups.csv.