diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index 9928568f414..bf3a8a5affc 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -1626,7 +1626,9 @@ def solve( legacy_soln.constraint[symbol]['Slack'] = val if hasattr(model, 'rc') and model.rc.import_enabled(): for v, val in results.solution_loader.get_reduced_costs().items(): - legacy_soln.variable['Rc'] = val + symbol = symbol_map.getSymbol(v) + if symbol in legacy_soln.variable: + legacy_soln.variable[symbol]['Rc'] = val legacy_results.solution.insert(legacy_soln) legacy_results._smap = symbol_map diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 9fc7b5f2e3d..4ecea98ac39 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -1410,16 +1410,22 @@ def test_load_solutions(self, name: str, opt_class: Type[PersistentSolver]): raise unittest.SkipTest m = pyo.ConcreteModel() m.x = pyo.Var() - m.obj = pyo.Objective(expr=m.x) + m.y = pyo.Var(bounds=(0, None)) + m.obj = pyo.Objective(expr=m.x + m.y) m.c = pyo.Constraint(expr=(-1, m.x, 1)) if opt_class != MAiNGO: m.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT) + m.rc = pyo.Suffix(direction=pyo.Suffix.IMPORT) res = opt.solve(m, load_solutions=False) pyo.assert_optimal_termination(res) self.assertIsNone(m.x.value) + self.assertIsNone(m.y.value) if opt_class != MAiNGO: self.assertNotIn(m.c, m.dual) + self.assertNotIn(m.y, m.rc) m.solutions.load_from(res) self.assertAlmostEqual(m.x.value, -1) + self.assertAlmostEqual(m.y.value, 0) if opt_class != MAiNGO: self.assertAlmostEqual(m.dual[m.c], 1) + self.assertAlmostEqual(m.rc[m.y], 1) diff --git a/pyomo/contrib/solver/common/base.py b/pyomo/contrib/solver/common/base.py index fd0d4f1bc18..e1ed7b62831 100644 --- a/pyomo/contrib/solver/common/base.py +++ b/pyomo/contrib/solver/common/base.py @@ -571,7 +571,9 @@ def _solution_handler( legacy_soln.constraint[symbol_map.getSymbol(con)] = {'Dual': val} if hasattr(model, 'rc') and model.rc.import_enabled(): for var, val in results.solution_loader.get_reduced_costs().items(): - legacy_soln.variable['Rc'] = val + symbol = symbol_map.getSymbol(var) + if symbol in legacy_soln.variable: + legacy_soln.variable[symbol]['Rc'] = val legacy_results.solution.insert(legacy_soln) legacy_results._smap_id = id(symbol_map) diff --git a/pyomo/contrib/solver/tests/solvers/test_solvers.py b/pyomo/contrib/solver/tests/solvers/test_solvers.py index 723aa39433c..0b30a6eb923 100644 --- a/pyomo/contrib/solver/tests/solvers/test_solvers.py +++ b/pyomo/contrib/solver/tests/solvers/test_solvers.py @@ -2497,16 +2497,22 @@ def test_load_solutions(self, name: str, opt_class: Type[SolverBase]): raise unittest.SkipTest(f'Solver {opt.name} not available.') m = pyo.ConcreteModel() m.x = pyo.Var() - m.obj = pyo.Objective(expr=m.x) + m.y = pyo.Var(bounds=(0, None)) + m.obj = pyo.Objective(expr=m.x + m.y) m.c = pyo.Constraint(expr=(-1, m.x, 1)) if (name, opt_class) in dual_solvers: m.dual = pyo.Suffix(direction=pyo.Suffix.IMPORT) + m.rc = pyo.Suffix(direction=pyo.Suffix.IMPORT) res = opt.solve(m, load_solutions=False) pyo.assert_optimal_termination(res) self.assertIsNone(m.x.value) + self.assertIsNone(m.y.value) if (name, opt_class) in dual_solvers: self.assertNotIn(m.c, m.dual) + self.assertNotIn(m.y, m.rc) m.solutions.load_from(res) self.assertAlmostEqual(m.x.value, -1) + self.assertAlmostEqual(m.y.value, 0) if (name, opt_class) in dual_solvers: self.assertAlmostEqual(m.dual[m.c], 1) + self.assertAlmostEqual(m.rc[m.y], 1)