@@ -131,7 +131,7 @@ def _con_lookup(m: Model) -> dict:
131131 return con_lookup
132132
133133
134- def bounds_to_constraints (self ) -> None :
134+ def bounds_to_constraints (m ) -> None :
135135 """
136136 Add explicit bound constraints for variables with bounds set directly
137137 in the variable rather than via explicit constraints.
@@ -141,45 +141,50 @@ def bounds_to_constraints(self) -> None:
141141
142142 Also resets variable bounds to [-inf, inf] after adding constraints,
143143 to avoid double-counting in the dual.
144+
145+ Parameters
146+ ----------
147+ m : Model
148+ Linopy model to convert variable bounds to constraints. Mutates the model in-place.
144149 """
145150 logger .debug ("Converting variable bounds to explicit constraints." )
146151 logger .debug ("Relaxing variable bounds to [-inf, inf]." )
147- for var_name , var in self .variables .items ():
152+ for var_name , var in m .variables .items ():
148153 mask = var .labels != - 1
149154 lb = var .lower
150155 ub = var .upper
151156
152157 # lower bound
153- if f"{ var_name } -bound-lower" not in self .constraints :
158+ if f"{ var_name } -bound-lower" not in m .constraints :
154159 has_finite_lb = np .isfinite (lb .values [mask .values ]).any ()
155160 if has_finite_lb :
156- self .add_constraints (
161+ m .add_constraints (
157162 var >= lb ,
158163 name = f"{ var_name } -bound-lower" ,
159164 mask = mask ,
160165 )
161166 logger .debug (f"Added lower bound constraint for '{ var_name } '." )
162167 var .lower .values [mask .values ] = - np .inf
163168 # Remove bounds to avoid double-counting in the dual. Rely on the new constraints instead.
164- self .variables [var_name ].lower .values [mask .values ] = - np .inf
169+ m .variables [var_name ].lower .values [mask .values ] = - np .inf
165170 else :
166171 logger .debug (
167172 f"Variable '{ var_name } ' has no finite lower bound, skipping."
168173 )
169174
170175 # upper bound
171- if f"{ var_name } -bound-upper" not in self .constraints :
176+ if f"{ var_name } -bound-upper" not in m .constraints :
172177 has_finite_ub = np .isfinite (ub .values [mask .values ]).any ()
173178 if has_finite_ub :
174- self .add_constraints (
179+ m .add_constraints (
175180 var <= ub ,
176181 name = f"{ var_name } -bound-upper" ,
177182 mask = mask ,
178183 )
179184 logger .debug (f"Added upper bound constraint for '{ var_name } '." )
180185 var .upper .values [mask .values ] = np .inf
181186 # Remove bounds to avoid double-counting in the dual. Rely on the new constraints instead.
182- self .variables [var_name ].upper .values [mask .values ] = np .inf
187+ m .variables [var_name ].upper .values [mask .values ] = np .inf
183188 else :
184189 logger .debug (
185190 f"Variable '{ var_name } ' has no finite upper bound, skipping."
@@ -303,7 +308,7 @@ def _build_dual_feas_terms(
303308 A = m .matrices .A
304309 if A is None :
305310 raise ValueError ("Constraint matrix is None, model has no constraints." )
306- A_csc = m . matrices . A .tocsc ()
311+ A_csc = A .tocsc ()
307312 c = m .matrices .c
308313 indptr = A_csc .indptr
309314 indices = A_csc .indices
@@ -466,7 +471,7 @@ def _add_dual_objective(
466471 a primal objective constant excluded via include_objective_constant=False
467472 during model creation. Default is 0.0.
468473 """
469- dual_obj = LinearExpression (None , m2 )
474+ dual_obj : LinearExpression = LinearExpression (None , m2 )
470475 sense = "max" if m .objective .sense == "min" else "min"
471476
472477 for name , con in m .constraints .items ():
@@ -487,7 +492,7 @@ def _add_dual_objective(
487492
488493
489494def dualize (
490- self ,
495+ m ,
491496 add_objective_constant : float = 0.0 ,
492497) -> Model :
493498 """
@@ -521,6 +526,9 @@ def dualize(
521526
522527 Parameters
523528 ----------
529+ m : Model
530+ Primal linopy model to dualize. Must have a linear objective and linear constraints.
531+
524532 add_objective_constant : float, optional
525533 Constant term to add to the dual objective. Use this to pass through
526534 a primal objective constant. Default is 0.0.
@@ -539,7 +547,7 @@ def dualize(
539547 """
540548 from linopy .model import Model
541549
542- m = self .copy ()
550+ m1 = m .copy ()
543551 m2 = Model ()
544552
545553 if not m .variables or not m .constraints :
@@ -548,10 +556,12 @@ def dualize(
548556 )
549557 return m2
550558
551- m .bounds_to_constraints ()
552- var_lup = _var_lookup (m )
553- con_lup = _con_lookup (m )
554- dual_vars = _add_dual_variables (m , m2 )
555- _add_dual_feasibility_constraints (m , m2 , dual_vars , var_lup , con_lup )
556- _add_dual_objective (m , m2 , dual_vars , add_objective_constant = add_objective_constant )
559+ m1 .bounds_to_constraints ()
560+ var_lup = _var_lookup (m1 )
561+ con_lup = _con_lookup (m1 )
562+ dual_vars = _add_dual_variables (m1 , m2 )
563+ _add_dual_feasibility_constraints (m1 , m2 , dual_vars , var_lup , con_lup )
564+ _add_dual_objective (
565+ m1 , m2 , dual_vars , add_objective_constant = add_objective_constant
566+ )
557567 return m2
0 commit comments