diff --git a/pyomo/core/plugins/transform/lp_dual.py b/pyomo/core/plugins/transform/lp_dual.py index c38c7c89f99..59ca6c89f71 100644 --- a/pyomo/core/plugins/transform/lp_dual.py +++ b/pyomo/core/plugins/transform/lp_dual.py @@ -16,6 +16,7 @@ from pyomo.common.dependencies import scipy from pyomo.core import ( ConcreteModel, + Block, Var, Constraint, Objective, @@ -47,6 +48,18 @@ def var_list(x): raise ValueError("Expected Var or list of Vars.\n\tReceived %s" % type(x)) +class _LPDualData(AutoSlots.Mixin): + __slots__ = ('primal_var', 'dual_var', 'primal_constraint', 'dual_constraint') + def __init__(self): + self.primal_var = {} + self.dual_var = {} + self.primal_constraint = ComponentMap() + self.dual_constraint = ComponentMap() + + +Block.register_private_data_initializer(_LPDualData) + + @TransformationFactory.register( 'core.lp_dual', 'Generate the linear programming dual of the given model' ) @@ -118,13 +131,17 @@ def _take_dual(self, model): rows = range(A_transpose.shape[0]) cols = range(A_transpose.shape[1]) dual.x = Var(cols, domain=NonNegativeReals) + trans_info = model.private_data() for j, (primal_cons, ineq) in enumerate(std_form.rows): if primal_sense is minimize and ineq == 1: dual.x[j].domain = NonPositiveReals - elif primal_sense is maximzie and ineq == -1: + elif primal_sense is maximize and ineq == -1: dual.x[j].domain = NonPositiveReals - from pytest import set_trace - set_trace() + if ineq == 0: + # equality + dual.x[j].domain = Reals + trans_info.primal_constraint[dual.x[j]] = primal_cons + trans_info.dual_var[primal_cons] = dual.x[j] dual.constraints = Constraint(rows) for i, primal in enumerate(std_form.columns): @@ -154,6 +171,8 @@ def _take_dual(self, model): dual.constraints[i] = ( sum(A_transpose[i, j] * dual.x[j] for j in cols) == std_form.c[0, i] ) + trans_info.dual_constraint[primal] = dual.constraints[i] + trans_info.primal_var[dual.constraints[i]] = primal dual.obj = Objective( expr=sum(std_form.rhs[j] * dual.x[j] for j in cols), sense=-primal_sense @@ -163,3 +182,41 @@ def _take_dual(self, model): def _take_parameterized_dual(self, model, wrt): pass + + def get_primal_constraint(self, model, dual_var): + primal_constraint = model.private_data().primal_constraint + if dual_var in primal_constraint: + return primal_constraint[dual_var] + else: + raise ValueError( + "It does not appear that Var '%s' is a dual variable on model '%s'" + % (dual_var.name, model.name) + ) + + def get_dual_constraint(self, model, primal_var): + dual_constraint = model.private_data().dual_constraint + if primal_var in dual_constraint: + return dual_constraint[primal_var] + else: + raise ValueError( + "It does not appear that Var '%s' is a primal variable from model '%s'" + % (primal_var.name, model.name) + ) + + def get_primal_var(self, model, dual_constraint): + primal_var = model.private_data().primal_var + if dual_constraint in primal_var: + return primal_var[dual_constraint] + else: + raise ValueError( + "It does not appear that Constraint '%s' is a dual constraint on " + "model '%s'" % (dual_constraint.name, model.name)) + + def get_dual_var(self, model, primal_constraint): + dual_var = model.private_data().dual_var + if primal_constraint in dual_var: + return dual_var[primal_constraint] + else: + raise ValueError( + "It does not appear that Constraint '%s' is a primal constraint from " + "model '%s'" % (primal_constraint.name, model.name)) diff --git a/pyomo/core/tests/unit/test_lp_dual.py b/pyomo/core/tests/unit/test_lp_dual.py index c3bfe74afe4..1bfdcf4c6cc 100644 --- a/pyomo/core/tests/unit/test_lp_dual.py +++ b/pyomo/core/tests/unit/test_lp_dual.py @@ -80,3 +80,50 @@ def test_lp_dual(self): lp_dual = TransformationFactory('core.lp_dual') dual = lp_dual.create_using(m) + + alpha = lp_dual.get_dual_var(m.c1) + beta = lp_dual.get_dual_var(m.c2) + lamb = lp_dual.get_dual_var(m.c3) + xi = lp_dual.get_dual_var(m.c4) + + self.assertIs(lp_dual.get_primal_constraint[alpha], m.c1) + self.assertIs(lp_dual.get_primal_constraint[beta], m.c2) + self.assertIs(lp_dual.get_primal_constraint[lamb], m.c3) + self.assertIs(lp_dual.get_primal_constraint[xi], m.c4) + + dx = lp_dual.get_dual_constraint[m.x] + dy = lp_dual.get_dual_constraint[m.y] + dz = lp_dual.get_dual_constraint[m.z] + + self.assertIs(lp_dual.get_primal_var[dx], m.x) + self.assertIs(lp_dual.get_primal_var[dy], m.y) + self.assertIs(lp_dual.get_primal_var[dz], m.z) + + self.assertIsInstance(alpha, Var) + self.assertIs(alpha.domain, NonPositiveReals) + self.assertEqual(alpha.ub, 0) + self.assertIsNone(alpha.lb) + self.assertIsInstance(beta, Var) + self.assertIs(alpha.domain, NonNegativeReals) + self.assertEqual(alpha.lb, 0) + self.assertIsNone(alpha.ub) + self.assertIsInstance(lamb, Var) + self.assertIs(lamb.domain, Reals) + self.assertIsNone(lamb.ub) + self.assertIsNone(lamb.lb) + self.assertIsInstance(xi, Var) + self.assertIs(xi.domain, NonPositiveReals) + self.assertEqual(xi.ub, 0) + self.assertIsNone(xi.lb) + + self.assertIsInstance(dx, Constraint) + self.assertIsInstance(dy, Constraint) + self.assertIsInstance(dz, Constraint) + + assertExpressionsEqual( + self, + dx.expr, + -4 * alpha + beta <= 1 + ) + + # TODO: map objectives, and test them