@@ -193,24 +193,21 @@ def bounds_to_constraints(m: Model) -> None:
193193
194194def _add_dual_variables (m : Model , m2 : Model ) -> dict :
195195 """
196- Add dual variables to m2 corresponding to constraints in m.
196+ Add dual variables to m2 corresponding to constraints in m..
197197
198198 For each active constraint in m, adds a dual variable to m2 following
199- linopy's sign convention:
199+ standard LP duality sign conventions. The sign of the dual variable bounds
200+ depends on both the constraint type and the primal objective sense:
200201
202+ For a minimization primal:
201203 - Equality constraints (=) -> free dual variable (lower=-inf, upper=inf)
202204 - <= constraints -> non-positive dual variable (lower=-inf, upper=0)
203205 - >= constraints -> non-negative dual variable (lower=0, upper=inf)
204206
205- This convention ensures that m2.variables[con_name].solution has the same
206- sign as m.constraints[con_name].dual after solving, allowing direct
207- comparison without sign adjustments.
208-
209- The sign encodes the direction of impact on the objective per unit RHS change:
210- - <= constraint dual (<=0): increasing RHS by 1 unit changes objective by
211- dual units (negative = cost decreases, i.e. relaxing the constraint).
212- - >= constraint dual (>=0): increasing RHS by 1 unit changes objective by
213- dual units (positive = cost increases, i.e. tightening the constraint).
207+ For a maximization primal:
208+ - Equality constraints (=) -> free dual variable (lower=-inf, upper=inf)
209+ - <= constraints -> non-negative dual variable (lower=0, upper=inf)
210+ - >= constraints -> non-positive dual variable (lower=-inf, upper=0)
214211
215212 Skips constraints with no active rows (empty or fully masked).
216213
@@ -510,19 +507,21 @@ def dualize(
510507 """
511508 Construct the dual of a linopy LP model.
512509
513- Transforms the primal model into its dual equivalent m2 by:
514- 1. Converting variable bounds to explicit constraints
515- 2. Adding dual variables to m2 (one per active constraint)
516- 3. Adding dual feasibility constraints to m2 (one per primal variable)
517- 4. Adding the dual objective to m2
510+ Transforms the primal model into its dual equivalent m2 following
511+ standard LP duality theory. The dual sense is flipped relative to the
512+ primal (min -> max, max -> min), and dual variable bounds depend on
513+ both constraint type and primal objective sense.
514+
515+ For a minimization primal:
518516
519- The dual is constructed following standard LP duality theory:
517+ Primal (min): Dual (max):
518+ min c^T x max b_eq^T λ + b_leq^T μ + b_geq^T ν
519+ s.t. A_eq x = b_eq : λ free s.t. A_eq^T λ + A_leq^T μ + A_geq^T ν = c
520+ A_leq x <= b_leq : μ <= 0 λ free, μ <= 0, ν >= 0
521+ A_geq x >= b_geq : ν >= 0
520522
521- Primal (min): Dual (max):
522- min c^T x max b_eq^T λ + b_leq^T μ + b_geq^T ν
523- s.t. A_eq x = b_eq : λ free s.t. A_eq^T λ + A_leq^T μ + A_geq^T ν = c
524- A_leq x <= b_leq : μ <= 0 λ free, μ <= 0, ν >= 0
525- A_geq x >= b_geq : ν >= 0
523+ For a maximization primal the dual variable bounds are flipped:
524+ μ >= 0 for <= constraints, ν <= 0 for >= constraints.
526525
527526 Variable bounds are converted to explicit constraints before dualization
528527 via bounds_to_constraints(), so that they appear in the constraint matrix
@@ -535,6 +534,7 @@ def dualize(
535534 primal objective = dual objective
536535
537536 Note: The standalone dual m2 may be unbounded if the primal is degenerate.
537+ Only linear programs (LP) are supported.
538538
539539 Parameters
540540 ----------
0 commit comments