Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .claude/skills/model-calibration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,14 @@ historical centre once the chain absorbs its gaps cleanly.
a lower triangle (feed disables the other four, food_waste the last
three, and so on); preserve it when adding a calibration.

The trap is that a step need not *enable* a successor's calibration to
depend on it -- inheriting `l1_cost: "calibrated"` from `default.yaml` is
enough. feed/food_waste/food_demand therefore set
`deviation_penalty.enabled: false` (production is pinned to actuals, so the
penalty has nothing to act on) and cost drives stability through hard
bounds. When adding a calibrated key to `default.yaml`, check what the
earlier step configs inherit.

- **Step configs must not override structural model keys.** Anything a
step config pins that isn't calibration machinery, the validation
regime, or a solver budget silently defeats the point of a per-base
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ introduce breaking changes to configuration and outputs.

### Fixed

- The feed, food_waste, food_demand and cost calibration steps no longer depend
on the calibrated deviation penalty, which the stability step produces at the
end of the chain. The first three pin production to actuals, so the penalty
had nothing to act on; the cost step drives production stability through hard
bounds, which never read the calibrated L1 costs. Regenerating the artefact
sets confirms the dependency was inert: every artefact is unchanged. The
calibration chain is a strict forward pass again, and
`tools/calibrate --check` settles after a full run instead of reporting the
first four steps stale.

- Workflow startup no longer builds a full copy of the configuration for every
configured scenario when deciding whether health data is needed. On configs
with generated scenario ensembles this dominated DAG construction: for
Expand Down
6 changes: 6 additions & 0 deletions config/calibration/feed.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ food_demand_calibration:
food_loss_waste_calibration:
enabled: false

# Production is pinned to actuals here, so the deviation penalty has nothing
# to act on. Leaving it enabled would make this step read the calibrated L1
# costs, which the stability step produces at the end of the chain.
deviation_penalty:
enabled: false

grazing:
grassland_forage_calibration:
enabled: false # Don't apply calibration (we're generating it)
Expand Down
6 changes: 6 additions & 0 deletions config/calibration/food_demand.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ food_utility_piecewise:
cost_calibration:
enabled: false

# Production is pinned to actuals here, so the deviation penalty has nothing
# to act on. Leaving it enabled would make this step read the calibrated L1
# costs, which the stability step produces at the end of the chain.
deviation_penalty:
enabled: false

food_demand_calibration:
enabled: false # Don't apply calibration (we're generating it)
generate: true # Generate calibration from solved model
Expand Down
6 changes: 6 additions & 0 deletions config/calibration/food_waste.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ food_utility_piecewise:
cost_calibration:
enabled: false

# Production is pinned to actuals here, so the deviation penalty has nothing
# to act on. Leaving it enabled would make this step read the calibrated L1
# costs, which the stability step produces at the end of the chain.
deviation_penalty:
enabled: false

# food_demand calibration depends on food_loss_waste.csv, which is built
# from this step's output. Disable to break the cycle: food_waste's slack
# must reflect the gap *without* the demand multiplier applied (the
Expand Down
2 changes: 1 addition & 1 deletion data/curated/calibration/default/deviation_penalty.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ l1_costs:
cropland: 1.4406072051746193
grassland: 0.23976429357967677
feed: 0.06151640376563935
iterations: 1
iterations: 0
converged: true
final_residual_log_inf: 0.019950027731896344
177 changes: 108 additions & 69 deletions data/curated/calibration/default/fingerprint.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions data/curated/calibration/default/provenance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# tools/calibrate; do not edit by hand.
# Licensing: see the annotation in REUSE.toml.
base_config: config/default.yaml
generated_at: '2026-07-28T17:44:34+00:00'
git_commit: e62c3a149e0bc4a9b103ae49c6af12b9e62a254f
generated_at: '2026-07-28T22:22:47+00:00'
git_commit: 53225fd85a4fa4ec491f92515226a9d09f790073
source: default
structural_config:
aggregation.irrigated_area_source: current
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ l1_costs:
cropland: 1.606710895127586
grassland: 0.24247737358625074
feed: 0.05417951839730158
iterations: 1
iterations: 0
converged: true
final_residual_log_inf: 0.018045776200625075
187 changes: 113 additions & 74 deletions data/curated/calibration/gbd-anchored/fingerprint.yaml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions data/curated/calibration/gbd-anchored/provenance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# tools/calibrate; do not edit by hand.
# Licensing: see the annotation in REUSE.toml.
base_config: config/gsa.yaml
generated_at: '2026-07-28T18:00:37+00:00'
git_commit: e62c3a149e0bc4a9b103ae49c6af12b9e62a254f
generated_at: '2026-07-28T22:30:03+00:00'
git_commit: 53225fd85a4fa4ec491f92515226a9d09f790073
source: gbd-anchored
structural_config:
aggregation.irrigated_area_source: current
Expand Down
9 changes: 8 additions & 1 deletion workflow/rules/common.smk
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,16 @@ def calibration_artefact_inputs(cfg):
if fd_cal_cfg["enabled"]:
inputs["food_demand_calibration"] = fd_cal_cfg["calibration_file"]

# Mirrors the solve's own gate: the sentinel is resolved only for an
# enabled L1 penalty, so anything else never opens the file.
dp_cfg = cfg["deviation_penalty"]
dp_cal_cfg = dp_cfg["calibration"]
if dp_cal_cfg["enabled"] and deviation_penalty_uses_calibrated(dp_cfg):
if (
dp_cal_cfg["enabled"]
and dp_cfg["enabled"]
and dp_cfg["penalty_mode"] == "l1"
and deviation_penalty_uses_calibrated(dp_cfg)
):
inputs["deviation_penalty_calibration"] = dp_cal_cfg["calibrated_yaml"]

return inputs
Loading