Skip to content

Fix BundledLeontief input usage to respect substitution bundles#67

Open
jose-moran wants to merge 5 commits into
bugs/productivity-growthfrom
bugs/substitution-stock-behavior
Open

Fix BundledLeontief input usage to respect substitution bundles#67
jose-moran wants to merge 5 commits into
bugs/productivity-growthfrom
bugs/substitution-stock-behavior

Conversation

@jose-moran

@jose-moran jose-moran commented Mar 6, 2026

Copy link
Copy Markdown
Member

Summary

My own summary here:
I had Claude look at the issue opened by @sternluke and reproduce the bug. There was problematic behaviour -- goods that became more expensive in the substitution bundle were actually increasing in stock during a simulation. Checking the rules we had, I noticed that the problem was that in the bundled Leontief production function good usage was done as with the standard Leontief. This means that used_intermediate_inputs was being computed as for the standard Leontief function -- substitution was applied for computing production and deciding how much to purchase, but not to update inventories. Paradoxically this led to the accumulation of stocks of expensive inputs.

This is now fixed, and I have added tests that BREAK before the fix and that now pass. I would advise checking that other behaviour is not affected.

LLM summary below:
Fixes #62intermediate_inputs_stock and capital_inputs_stock behaved differently for substitutable vs non-substitutable goods.

  • BundledLeontief.compute_intermediate_inputs_used() and compute_capital_inputs_used() ignored the substitution bundle matrix entirely, computing usage at fixed Leontief rates per good. Meanwhile, buying targets were adjusted by substitution weights (price-based reweighting). This mismatch caused expensive goods' stocks to deplete rapidly and cheap goods' stocks to accumulate, since firms bought less of expensive goods but still "used" them at the original rate.
  • Now distributes total bundle usage proportionally to available stock within each multi-member bundle. Firms use more of whichever substitute they have on hand, consistent with how the production capacity calculation already treats bundle goods as substitutes.
  • Before the fix, in a 20-period Canadian disagg simulation with energy bundle (B05a, B05b, B05c, C19), B05a stock dropped -81% while C19 grew +39%. After the fix, stock changes are moderate (+4% to +35%) and correctly inversely correlated with relative prices within the bundle.

Test plan

  • Two new unit tests in test_production.py that fail before the fix and pass after:
    • test_usage_distributed_by_stock_within_bundle — verifies usage shares match stock shares within a bundle
    • test_usage_total_preserved_within_bundle — verifies total bundle usage is conserved, just redistributed
  • All 32 simulation tests pass
  • Reproduction script in debug_scripts/reproduce_bug_62.py confirms improved behavior

cc @lukehclarke

jose-moran and others added 2 commits March 6, 2026 19:35
BundledLeontief.compute_intermediate_inputs_used() and
compute_capital_inputs_used() ignored the substitution bundle matrix,
using fixed Leontief coefficients for each good independently. This
caused stock to deplete for expensive goods and accumulate for cheap
goods, since buying was adjusted by substitution weights but usage
was not.

Now distributes total bundle usage proportionally to available stock
within each multi-member bundle, so firms use more of whichever
substitute they have on hand.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jose-moran jose-moran requested a review from sternluke March 6, 2026 18:44
@sternluke

sternluke commented Mar 9, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this Jose

I see significant production behaviour changes with these input usage changes which are especially apparent after a price shock (vertical dotted line) where electricity prices are reduced in comparison to fossil fuels which should encourage substitution towards electricity (dashed line) vs a reference scenario (solid line).

Here is energy production with these changes:
stock - energy_production - update

and energy production without these changes:
stock - energy_production - no update

Here is non-energy production with these changes:
stock - non_energy_production - update

and non-energy production without these changes:
stock - non_energy_production - no update

All that being said, the behaviour of used intermediate inputs appears more logical with these changes:
stock - used_intermediate_input - update

and used intermediate inputs without these changes:
stock - used_intermediate_input - no update

And also looks better for intermediate input stock with these change:
stock - intermediate_inputs_stock - update

than without:
stock - intermediate_inputs_stock - no update

@sternluke

Copy link
Copy Markdown
Collaborator

My gut is that these changes are promising but there is something in them that is triggering production crashes when subjected to a price shock.

@jose-moran

Copy link
Copy Markdown
Member Author

how is the price shock defined precisely?

@sternluke

sternluke commented Mar 9, 2026

Copy link
Copy Markdown
Collaborator

The price shock is executed through a exogenous pricing function that we are using for the energy bundle which imports a price timeseries for those sectors rather than using the endogenous prices in firms/price.py and row/prices.py. It is useful to set up controlled substitution experiments like this one.

Let me know if you'd like more specific details.

@jose-moran

Copy link
Copy Markdown
Member Author

@sternluke can you try again?

@sternluke

sternluke commented Mar 9, 2026

Copy link
Copy Markdown
Collaborator

Sure thing.

With the updated approach, I am seeing no productivity crashes but substitution behaviour has been significantly dampened to the point where the there is very little difference between production with in the cheap electricity scenario (dash line) vs reference scenario (solid line).

Here is energy production with the change:
stock 2 - energy production - update

and without:
stock 2 - energy production - no update

Here is the energy per unit iron and steel with the change:
stock 2 - energy per unit iron - update

and without:
stock 2 - energy per unit iron - no update

There is also a significant change in inventory with the change:
stock 2 - inventory - update

and without:
stock 2 - inventory - no update

I'm also seeing climbing prices:
stock 2 - non-energy prices - update

and without:
stock 2 - non-energy prices - no update

Used intermediate inputs makes sense with the changes
stock 2 - intermediate inputs stock - update

but intermediate input stock doesn't seem to tell a consistent story to inventory
stock 2 - used intermediate inputs - update

@jose-moran

Copy link
Copy Markdown
Member Author

what's the exact experiment you are doing for with/without and see climbing energy prices?

@sternluke

sternluke commented Mar 10, 2026

Copy link
Copy Markdown
Collaborator

The pricing experiment overwrites price for B05a, B05b, B05c, C19, and the D01* sectors in prices.py (for both firms and ROW) according to the attached exo_prices_scenarios.xlsx which are historical and projected energy prices normalized to the first timestep price. The overwritten energy prices are the same in both scenarios until timestep 45 when the price of only the D01* sectors is reduced by 20% in the Expanded Electricity Transmission Scenario in comparison with the Reference Scenario (these elements have been highlighted in the xlsx file for clarity).

exo_prices_scenarios.xlsx

stock 2 - energy prices - no update

Likely the non-energy price increase that I mentioned above is related to the fact that the energy prices cannot increase so the only place that inflationary pressure can go is the prices which are not set exogenously. While I thought that it was worth pointing this behaviour out, the issue that I found more troubling was the significant dampening of the substitution behaviour which almost eliminates the difference between the two scenarios (ie the difference between the solid and dashed lines in the production plots from the previous post).

My main observation is that any changes to the compute_intermediate_inputs_used() and compute_capital_inputs_used() should preserve the ability to differentiate production behaviour between situations with 20% differences in electricity prices.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants