@@ -50,10 +50,10 @@ class Effect(Element):
5050 without effect dictionaries. Used for simplified effect specification (and less boilerplate code).
5151 is_objective: If True, this effect serves as the optimization objective function.
5252 Only one effect can be marked as objective per optimization.
53- specific_share_to_other_effects_operation: Operational cross-effect contributions.
54- Maps this effect's operational values to contributions to other effects
55- specific_share_to_other_effects_invest: Investment cross-effect contributions.
56- Maps this effect's investment values to contributions to other effects .
53+ share_from_temporal: Temporal cross-effect contributions.
54+ Maps temporal contributions from other effects to this effect
55+ share_from_nontemporal: Nontemporal cross-effect contributions.
56+ Maps nontemporal contributions from other effects to this effect .
5757 minimum_temporal: Minimum allowed total contribution across all timesteps.
5858 maximum_temporal: Maximum allowed total contribution across all timesteps.
5959 minimum_per_hour: Minimum allowed contribution per hour.
@@ -77,17 +77,21 @@ class Effect(Element):
7777 Basic cost objective:
7878
7979 ```python
80- cost_effect = Effect(label='system_costs', unit='€', description='Total system costs', is_objective=True)
80+ cost_effect = Effect(
81+ label='system_costs',
82+ unit='€',
83+ description='Total system costs',
84+ is_objective=True,
85+ )
8186 ```
8287
83- CO2 emissions with carbon pricing :
88+ CO2 emissions:
8489
8590 ```python
8691 co2_effect = Effect(
87- label='co2_emissions ',
92+ label='CO2 ',
8893 unit='kg_CO2',
8994 description='Carbon dioxide emissions',
90- specific_share_to_other_effects_operation={'costs': 50}, # €50/t_CO2
9195 maximum_total=1_000_000, # 1000 t CO2 annual limit
9296 )
9397 ```
@@ -110,7 +114,21 @@ class Effect(Element):
110114 label='primary_energy',
111115 unit='kWh_primary',
112116 description='Primary energy consumption',
113- specific_share_to_other_effects_operation={'costs': 0.08}, # €0.08/kWh
117+ )
118+ ```
119+
120+ Cost objective with carbon and primary energy pricing:
121+
122+ ```python
123+ cost_effect = Effect(
124+ label='system_costs',
125+ unit='€',
126+ description='Total system costs',
127+ is_objective=True,
128+ share_from_temporal={
129+ 'primary_energy': 0.08, # 0.08 €/kWh_primary
130+ 'CO2': 0.2, # Carbon pricing: 0.2 €/kg_CO2 into costs if used on a cost effect
131+ },
114132 )
115133 ```
116134
@@ -137,8 +155,7 @@ class Effect(Element):
137155 across all contributions to each effect manually.
138156
139157 Effects are accumulated as:
140- - Total = Σ(operational contributions) + Σ(investment contributions)
141- - Cross-effects add to target effects based on specific_share ratios
158+ - Total = Σ(temporal contributions) + Σ(nontemporal contributions)
142159
143160 """
144161
@@ -150,8 +167,8 @@ def __init__(
150167 meta_data : dict | None = None ,
151168 is_standard : bool = False ,
152169 is_objective : bool = False ,
153- specific_share_to_other_effects_operation : TemporalEffectsUser | None = None ,
154- specific_share_to_other_effects_invest : NonTemporalEffectsUser | None = None ,
170+ share_from_temporal : TemporalEffectsUser | None = None ,
171+ share_from_nontemporal : NonTemporalEffectsUser | None = None ,
155172 minimum_temporal : NonTemporalEffectsUser | None = None ,
156173 maximum_temporal : NonTemporalEffectsUser | None = None ,
157174 minimum_nontemporal : NonTemporalEffectsUser | None = None ,
@@ -167,11 +184,9 @@ def __init__(
167184 self .description = description
168185 self .is_standard = is_standard
169186 self .is_objective = is_objective
170- self .specific_share_to_other_effects_operation : TemporalEffectsUser = (
171- specific_share_to_other_effects_operation if specific_share_to_other_effects_operation is not None else {}
172- )
173- self .specific_share_to_other_effects_invest : NonTemporalEffectsUser = (
174- specific_share_to_other_effects_invest if specific_share_to_other_effects_invest is not None else {}
187+ self .share_from_temporal : TemporalEffectsUser = share_from_temporal if share_from_temporal is not None else {}
188+ self .share_from_nontemporal : NonTemporalEffectsUser = (
189+ share_from_nontemporal if share_from_nontemporal is not None else {}
175190 )
176191
177192 # Handle backwards compatibility for deprecated parameters
@@ -393,8 +408,17 @@ def transform_data(self, flow_system: FlowSystem, name_prefix: str = '') -> None
393408
394409 self .maximum_per_hour = flow_system .fit_to_model_coords (f'{ prefix } |maximum_per_hour' , self .maximum_per_hour )
395410
396- self .specific_share_to_other_effects_operation = flow_system .fit_effects_to_model_coords (
397- f'{ prefix } |operation->' , self .specific_share_to_other_effects_operation , 'temporal'
411+ self .share_from_temporal = flow_system .fit_effects_to_model_coords (
412+ label_prefix = None ,
413+ effect_values = self .share_from_temporal ,
414+ label_suffix = f'(temporal)->{ prefix } (temporal)' ,
415+ dims = ['time' , 'year' , 'scenario' ],
416+ )
417+ self .share_from_nontemporal = flow_system .fit_effects_to_model_coords (
418+ label_prefix = None ,
419+ effect_values = self .share_from_nontemporal ,
420+ label_suffix = f'(nontemporal)->{ prefix } (nontemporal)' ,
421+ dims = ['year' , 'scenario' ],
398422 )
399423
400424 self .minimum_temporal = flow_system .fit_to_model_coords (
@@ -417,12 +441,6 @@ def transform_data(self, flow_system: FlowSystem, name_prefix: str = '') -> None
417441 self .maximum_total = flow_system .fit_to_model_coords (
418442 f'{ prefix } |maximum_total' , self .maximum_total , dims = ['year' , 'scenario' ]
419443 )
420- self .specific_share_to_other_effects_invest = flow_system .fit_effects_to_model_coords (
421- f'{ prefix } |operation->' ,
422- self .specific_share_to_other_effects_invest ,
423- 'operation' ,
424- dims = ['year' , 'scenario' ],
425- )
426444
427445 def create_model (self , model : FlowSystemModel ) -> EffectModel :
428446 self ._plausibility_checks ()
@@ -569,6 +587,11 @@ def _plausibility_checks(self) -> None:
569587 # Check circular loops in effects:
570588 temporal , nontemporal = self .calculate_effect_share_factors ()
571589
590+ # Validate all referenced sources exist
591+ unknown = {src for src , _ in list (temporal .keys ()) + list (nontemporal .keys ()) if src not in self .effects }
592+ if unknown :
593+ raise KeyError (f'Unknown effects used in in effect share mappings: { sorted (unknown )} ' )
594+
572595 temporal_cycles = detect_cycles (tuples_to_adjacency_list ([key for key in temporal ]))
573596 nontemporal_cycles = detect_cycles (tuples_to_adjacency_list ([key for key in nontemporal ]))
574597
@@ -656,18 +679,20 @@ def calculate_effect_share_factors(
656679 ]:
657680 shares_nontemporal = {}
658681 for name , effect in self .effects .items ():
659- if effect .specific_share_to_other_effects_invest :
660- shares_nontemporal [name ] = {
661- target : data for target , data in effect .specific_share_to_other_effects_invest .items ()
662- }
682+ if effect .share_from_nontemporal :
683+ for source , data in effect .share_from_nontemporal .items ():
684+ if source not in shares_nontemporal :
685+ shares_nontemporal [source ] = {}
686+ shares_nontemporal [source ][name ] = data
663687 shares_nontemporal = calculate_all_conversion_paths (shares_nontemporal )
664688
665689 shares_temporal = {}
666690 for name , effect in self .effects .items ():
667- if effect .specific_share_to_other_effects_operation :
668- shares_temporal [name ] = {
669- target : data for target , data in effect .specific_share_to_other_effects_operation .items ()
670- }
691+ if effect .share_from_temporal :
692+ for source , data in effect .share_from_temporal .items ():
693+ if source not in shares_temporal :
694+ shares_temporal [source ] = {}
695+ shares_temporal [source ][name ] = data
671696 shares_temporal = calculate_all_conversion_paths (shares_temporal )
672697
673698 return shares_temporal , shares_nontemporal
@@ -726,19 +751,19 @@ def _do_modeling(self):
726751 )
727752
728753 def _add_share_between_effects (self ):
729- for origin_effect in self .effects :
730- # 1. temporal: -> hier sind es Zeitreihen (share_TS)
731- for target_effect , time_series in origin_effect . specific_share_to_other_effects_operation .items ():
732- self . effects [ target_effect ] .submodel .temporal .add_share (
733- origin_effect .submodel .temporal .label_full ,
734- origin_effect .submodel .temporal .total_per_timestep * time_series ,
754+ for target_effect in self .effects :
755+ # 1. temporal: <- receiving temporal shares from other effects
756+ for source_effect , time_series in target_effect . share_from_temporal .items ():
757+ target_effect .submodel .temporal .add_share (
758+ self . effects [ source_effect ] .submodel .temporal .label_full ,
759+ self . effects [ source_effect ] .submodel .temporal .total_per_timestep * time_series ,
735760 dims = ('time' , 'year' , 'scenario' ),
736761 )
737- # 2. nontemporal: -> hier ist es Scalar (share)
738- for target_effect , factor in origin_effect . specific_share_to_other_effects_invest .items ():
739- self . effects [ target_effect ] .submodel .nontemporal .add_share (
740- origin_effect .submodel .nontemporal .label_full ,
741- origin_effect .submodel .nontemporal .total * factor ,
762+ # 2. nontemporal: <- receiving nontemporal shares from other effects
763+ for source_effect , factor in target_effect . share_from_nontemporal .items ():
764+ target_effect .submodel .nontemporal .add_share (
765+ self . effects [ source_effect ] .submodel .nontemporal .label_full ,
766+ self . effects [ source_effect ] .submodel .nontemporal .total * factor ,
742767 dims = ('year' , 'scenario' ),
743768 )
744769
0 commit comments