You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
compute_effect_contributions() in src/fluxopt/contributions.py re-implements the same multiplication logic that _create_effects() in src/fluxopt/model.py uses to build the lump and temporal expressions. The two paths must stay in lockstep — every time we add a new cost type to the model, we have to remember to mirror it in contributions, or the validation check fails (this is what #84 was about; PR #135 fixed Investment, but the duplication remains).
This issue proposes eliminating the duplication by storing per-contributor expressions during model build and evaluating them after solve.
Current architecture
In _create_effects():
# Each term is built per-flow then summed before becoming a constraintlump_direct=lump_direct+ (eps*self.flow_size.sel(flow=sizing_ids)).sum('flow')
lump_direct=lump_direct+ (ef*self.flow_size_indicator).sum('flow')
# ... 6 more similar blocks for sizing fixed, storage, investment (4 types)self.m.add_constraints(self.effect_lump==lump_rhs, name='effect_lump_eq')
The .sum('flow') collapses contributor dim before the expression becomes a variable. The solver only sees effect_lump[effect, period].
In compute_effect_contributions(), the same multiplications are redone on solved values without the .sum('flow'):
Store the un-summed expressions during model build, evaluate .solution after solve.
Linopy LinearExpression has a .solution property that returns the numeric value of the expression with current solver values. This means we can:
In _create_effects(), build per-contributor expressions WITHOUT collapsing flow:
per_flow_lump=eps*self.flow_size.sel(flow=sizing_ids) # has 'flow' dimself._lump_contrib_terms.append(per_flow_lump) # store, don't collapselump_direct=lump_direct+per_flow_lump.sum('flow') # constraint as before
Background
compute_effect_contributions()insrc/fluxopt/contributions.pyre-implements the same multiplication logic that_create_effects()insrc/fluxopt/model.pyuses to build the lump and temporal expressions. The two paths must stay in lockstep — every time we add a new cost type to the model, we have to remember to mirror it in contributions, or the validation check fails (this is what #84 was about; PR #135 fixed Investment, but the duplication remains).This issue proposes eliminating the duplication by storing per-contributor expressions during model build and evaluating them after solve.
Current architecture
In
_create_effects():The
.sum('flow')collapses contributor dim before the expression becomes a variable. The solver only seeseffect_lump[effect, period].In
compute_effect_contributions(), the same multiplications are redone on solved values without the.sum('flow'):This duplication is brittle.
Proposed approach
Store the un-summed expressions during model build, evaluate
.solutionafter solve.Linopy
LinearExpressionhas a.solutionproperty that returns the numeric value of the expression with current solver values. This means we can:In
_create_effects(), build per-contributor expressions WITHOUT collapsing flow:After solve, in contributions.py:
Delete
_compute_sizing_lump(),_compute_investment_lump(), and most of_compute_periodic(). Keep only_leontief()and_apply_leontief().Pre-work: verify linopy supports this
Before refactoring, confirm:
LinearExpression.solutionworks for products of variables and DataArray coefficients (it should — linopy uses this internally forVariable.solution)A quick spike:
Files to touch
src/fluxopt/model.py—_create_effects()(~lines 728-905): refactor each term to also store the per-contributor expressionsrc/fluxopt/contributions.py— replace most ofcompute_effect_contributionswith.solutionlookups + Leontieftests/math/test_contributions.py— should pass unchanged if the math is righttests/math_port/test_multi_period.py— the validation-pipeline xfail case (fix: effect_contributions Leontief inverse fails for period-varying contribution_from #134) might also be fixedWhat this does NOT solve
_leontief()/_apply_leontief()for the period batch dim. But this refactor might make it easier to fix because contributions.py becomes simpler.Verification
Specifically watch for:
Out of scope
Result.stats.effect_contributionskeys:temporal,lump,total)_create_effects(just touch the lump/temporal accumulation)Trade-offs
Pros
contributions.py(rough estimate: ~80 lines deleted)Cons
compute_effect_contributionsnow depends on FlowSystem internals (the stored term list), not justModelData+ solutionRelated
compute_effect_contributionsusing the duplicated approach (good interim fix, but motivates this refactor)