Summary
Rename the cross-effect fields on Effect to domain-explicit names and add the missing cross_once for one-time investment costs.
Current API (to be removed)
Effect(
'cost',
contribution_from={'co2': 50}, # → cf_periodic
contribution_from_per_hour={'co2': 50}, # → cf_temporal
)
Problems:
contribution_from sounds like it applies to everything, but only covers the periodic domain (sizing, recurring investment)
contribution_from_per_hour hints at temporal but the mapping is implicit
- No way to price one-time investment costs (embodied emissions, etc.) through cross-effects
New API
Effect(
'cost',
cross_temporal={'co2': 50}, # per-timestep domain
cross_periodic={'co2': 50}, # sizing + recurring investment domain
cross_once={'co2': 50}, # one-time investment domain
)
Hard rename, no deprecation (pre-v1 alpha).
Mapping
Effect field |
EffectsData matrix |
Applied to |
Domain |
cross_temporal |
cf_temporal (existing) |
effect_temporal |
operational per-timestep |
cross_periodic |
cf_periodic (existing) |
effect_periodic |
sizing, recurring investment |
cross_once |
cf_once (new) |
effect_once |
one-time investment |
Implementation
1. elements.py — rename fields on Effect
@dataclass
class Effect:
# ...
cross_temporal: dict[str, TimeSeries] = field(default_factory=dict)
cross_periodic: dict[str, TimeSeries] = field(default_factory=dict)
cross_once: dict[str, TimeSeries] = field(default_factory=dict)
Remove contribution_from and contribution_from_per_hour.
2. model_data.py — build cf_once matrix
EffectsData.build() already constructs cf_temporal and cf_periodic from the old fields. Add parallel construction for cf_once from cross_once. Add cf_once: xr.DataArray | None to EffectsData.
3. model.py — apply Leontief to effect_once
In _create_effects(), after computing once_direct, apply cf_once Leontief:
once_rhs: Any = once_direct
if ds.cf_once is not None:
source_o = self.effect_once.rename({'effect': 'source_effect'})
cross = (ds.cf_once * source_o).sum('source_effect')
once_rhs = cross + once_direct
self.m.add_constraints(self.effect_once == once_rhs, name='effect_once_eq')
4. contributions.py — apply Leontief to once domain
if data.effects.cf_once is not None:
once = _apply_leontief(_leontief(data.effects.cf_once), once)
5. io.py — serialize cf_once
Add cf_once to the effects dataset serialization, parallel to cf_temporal / cf_periodic.
6. Tests — update all cross-effect tests
- Rename
contribution_from= → cross_periodic= in all test files
- Rename
contribution_from_per_hour= → cross_temporal=
- Add tests for
cross_once with Investment one-time costs
- Verify the existing
test_contribution_from_with_investment_once_costs works with cross_once
Files to change
src/fluxopt/elements.py — field rename
src/fluxopt/model_data.py — build cf_once, rename field references
src/fluxopt/model.py — apply cf_once Leontief to effect_once
src/fluxopt/contributions.py — apply cf_once Leontief to once contributions
src/fluxopt/io.py — serialize/deserialize cf_once
tests/ — rename all contribution_from → cross_periodic etc.
Summary
Rename the cross-effect fields on
Effectto domain-explicit names and add the missingcross_oncefor one-time investment costs.Current API (to be removed)
Problems:
contribution_fromsounds like it applies to everything, but only covers the periodic domain (sizing, recurring investment)contribution_from_per_hourhints at temporal but the mapping is implicitNew API
Hard rename, no deprecation (pre-v1 alpha).
Mapping
EffectfieldEffectsDatamatrixcross_temporalcf_temporal(existing)effect_temporalcross_periodiccf_periodic(existing)effect_periodiccross_oncecf_once(new)effect_onceImplementation
1.
elements.py— rename fields onEffectRemove
contribution_fromandcontribution_from_per_hour.2.
model_data.py— buildcf_oncematrixEffectsData.build()already constructscf_temporalandcf_periodicfrom the old fields. Add parallel construction forcf_oncefromcross_once. Addcf_once: xr.DataArray | NonetoEffectsData.3.
model.py— apply Leontief toeffect_onceIn
_create_effects(), after computingonce_direct, applycf_onceLeontief:4.
contributions.py— apply Leontief to once domain5.
io.py— serializecf_onceAdd
cf_onceto the effects dataset serialization, parallel tocf_temporal/cf_periodic.6. Tests — update all cross-effect tests
contribution_from=→cross_periodic=in all test filescontribution_from_per_hour=→cross_temporal=cross_oncewith Investment one-time coststest_contribution_from_with_investment_once_costsworks withcross_onceFiles to change
src/fluxopt/elements.py— field renamesrc/fluxopt/model_data.py— buildcf_once, rename field referencessrc/fluxopt/model.py— applycf_onceLeontief toeffect_oncesrc/fluxopt/contributions.py— applycf_onceLeontief to once contributionssrc/fluxopt/io.py— serialize/deserializecf_oncetests/— rename allcontribution_from→cross_periodicetc.