@@ -189,7 +189,7 @@ def expression_tracking_variable(
189189 name : str = None ,
190190 short_name : str = None ,
191191 bounds : Tuple [TemporalData , TemporalData ] = None ,
192- coords : List [str ] = None ,
192+ coords : Optional [ Union [ str , List [str ]] ] = None ,
193193 ) -> Tuple [linopy .Variable , linopy .Constraint ]:
194194 """
195195 Creates variable that equals a given expression.
@@ -205,8 +205,6 @@ def expression_tracking_variable(
205205 if not isinstance (model , Submodel ):
206206 raise ValueError ('ModelingPrimitives.expression_tracking_variable() can only be used with a Submodel' )
207207
208- coords = coords or ['year' , 'scenario' ]
209-
210208 if not bounds :
211209 tracker = model .add_variables (name = name , coords = model .get_coords (coords ), short_name = short_name )
212210 else :
@@ -223,86 +221,6 @@ def expression_tracking_variable(
223221
224222 return tracker , tracking
225223
226- @staticmethod
227- def state_transition_variables (
228- model : Submodel ,
229- state_variable : linopy .Variable ,
230- switch_on : linopy .Variable ,
231- switch_off : linopy .Variable ,
232- name : str ,
233- previous_state = 0 ,
234- ) -> Tuple [linopy .Constraint , linopy .Constraint , linopy .Constraint ]:
235- """
236- Creates switch-on/off variables with state transition logic.
237-
238- Mathematical formulation:
239- switch_on[t] - switch_off[t] = state[t] - state[t-1] ∀t > 0
240- switch_on[0] - switch_off[0] = state[0] - previous_state
241- switch_on[t] + switch_off[t] ≤ 1 ∀t
242- switch_on[t], switch_off[t] ∈ {0, 1}
243-
244- Returns:
245- variables: {'switch_on': binary_var, 'switch_off': binary_var}
246- constraints: {'transition': constraint, 'initial': constraint, 'mutex': constraint}
247- """
248- if not isinstance (model , Submodel ):
249- raise ValueError ('ModelingPrimitives.state_transition_variables() can only be used with a Submodel' )
250-
251- # State transition constraints for t > 0
252- transition = model .add_constraints (
253- switch_on .isel (time = slice (1 , None )) - switch_off .isel (time = slice (1 , None ))
254- == state_variable .isel (time = slice (1 , None )) - state_variable .isel (time = slice (None , - 1 )),
255- name = f'{ name } |transition' ,
256- )
257-
258- # Initial state transition for t = 0
259- initial = model .add_constraints (
260- switch_on .isel (time = 0 ) - switch_off .isel (time = 0 ) == state_variable .isel (time = 0 ) - previous_state ,
261- name = f'{ name } |initial' ,
262- )
263-
264- # At most one switch per timestep
265- mutex = model .add_constraints (switch_on + switch_off <= 1 , name = f'{ name } |mutex' )
266-
267- return transition , initial , mutex
268-
269- @staticmethod
270- def sum_up_variable (
271- model : Submodel ,
272- variable_to_count : linopy .Variable ,
273- name : str = None ,
274- bounds : Tuple [NonTemporalData , NonTemporalData ] = None ,
275- factor : TemporalData = 1 ,
276- ) -> Tuple [linopy .Variable , linopy .Constraint ]:
277- """
278- SUms up a variable over time, applying a factor to the variable.
279-
280- Args:
281- model: The optimization model instance
282- variable_to_count: The variable to be summed up
283- name: The name of the constraint
284- bounds: The bounds of the constraint
285- factor: The factor to be applied to the variable
286- """
287- if not isinstance (model , Submodel ):
288- raise ValueError ('ModelingPrimitives.sum_up_variable() can only be used with a Submodel' )
289-
290- if bounds is None :
291- bounds = (0 , np .inf )
292- else :
293- bounds = (bounds [0 ] if bounds [0 ] is not None else 0 , bounds [1 ] if bounds [1 ] is not None else np .inf )
294-
295- count = model .add_variables (
296- lower = bounds [0 ],
297- upper = bounds [1 ],
298- coords = model .get_coords (['year' , 'scenario' ]),
299- name = name ,
300- )
301-
302- count_constraint = model .add_constraints (count == (variable_to_count * factor ).sum ('time' ), name = name )
303-
304- return count , count_constraint
305-
306224 @staticmethod
307225 def consecutive_duration_tracking (
308226 model : Submodel ,
@@ -346,7 +264,7 @@ def consecutive_duration_tracking(
346264 duration = model .add_variables (
347265 lower = 0 ,
348266 upper = maximum_duration if maximum_duration is not None else mega ,
349- coords = model .get_coords ([ 'time' ] ),
267+ coords = model .get_coords (),
350268 name = name ,
351269 short_name = short_name ,
352270 )
@@ -618,10 +536,55 @@ def scaled_bounds_with_state(
618536 )
619537 scaling_upper = model .add_constraints (variable <= scaling_variable * rel_upper , name = f'{ name } |ub2' )
620538
621- big_m_upper = scaling_max * rel_upper
622- big_m_lower = np .maximum (CONFIG .modeling .EPSILON , scaling_min * rel_lower )
539+ big_m_upper = rel_upper * scaling_max
540+ big_m_lower = np .maximum (CONFIG .modeling .EPSILON , rel_lower * scaling_min )
623541
624542 binary_upper = model .add_constraints (variable_state * big_m_upper >= variable , name = f'{ name } |ub1' )
625543 binary_lower = model .add_constraints (variable_state * big_m_lower <= variable , name = f'{ name } |lb1' )
626544
627545 return [scaling_lower , scaling_upper , binary_lower , binary_upper ]
546+
547+ @staticmethod
548+ def state_transition_bounds (
549+ model : Submodel ,
550+ state_variable : linopy .Variable ,
551+ switch_on : linopy .Variable ,
552+ switch_off : linopy .Variable ,
553+ name : str ,
554+ previous_state = 0 ,
555+ coord : str = 'time' ,
556+ ) -> Tuple [linopy .Constraint , linopy .Constraint , linopy .Constraint ]:
557+ """
558+ Creates switch-on/off variables with state transition logic.
559+
560+ Mathematical formulation:
561+ switch_on[t] - switch_off[t] = state[t] - state[t-1] ∀t > 0
562+ switch_on[0] - switch_off[0] = state[0] - previous_state
563+ switch_on[t] + switch_off[t] ≤ 1 ∀t
564+ switch_on[t], switch_off[t] ∈ {0, 1}
565+
566+ Returns:
567+ variables: {'switch_on': binary_var, 'switch_off': binary_var}
568+ constraints: {'transition': constraint, 'initial': constraint, 'mutex': constraint}
569+ """
570+ if not isinstance (model , Submodel ):
571+ raise ValueError ('ModelingPrimitives.state_transition_variables() can only be used with a Submodel' )
572+
573+ # State transition constraints for t > 0
574+ transition = model .add_constraints (
575+ switch_on .isel ({coord : slice (1 , None )}) - switch_off .isel ({coord : slice (1 , None )})
576+ == state_variable .isel ({coord : slice (1 , None )}) - state_variable .isel ({coord : slice (None , - 1 )}),
577+ name = f'{ name } |transition' ,
578+ )
579+
580+ # Initial state transition for t = 0
581+ initial = model .add_constraints (
582+ switch_on .isel ({coord : 0 }) - switch_off .isel ({coord : 0 })
583+ == state_variable .isel ({coord : 0 }) - previous_state ,
584+ name = f'{ name } |initial' ,
585+ )
586+
587+ # At most one switch per timestep
588+ mutex = model .add_constraints (switch_on + switch_off <= 1 , name = f'{ name } |mutex' )
589+
590+ return transition , initial , mutex
0 commit comments