diff --git a/pyomo/contrib/appsi/base.py b/pyomo/contrib/appsi/base.py index e2a694cfbd6..d279b8b6015 100644 --- a/pyomo/contrib/appsi/base.py +++ b/pyomo/contrib/appsi/base.py @@ -1406,17 +1406,17 @@ def update(self, timer: HierarchicalTimer = None): vars_to_update = list() for v in vars_to_check: _v, lb, ub, fixed, domain_interval, value = self._vars[id(v)] - if lb is not v._lb: - vars_to_update.append(v) - elif ub is not v._ub: - vars_to_update.append(v) - elif (fixed is not v.fixed) or (fixed and (value != v.value)): + if (fixed != v.fixed) or (fixed and (value != v.value)): vars_to_update.append(v) if self.update_config.treat_fixed_vars_as_params: for c in self._referenced_variables[id(v)][0]: cons_to_remove_and_add[c] = None if self._referenced_variables[id(v)][2] is not None: need_to_set_objective = True + elif lb is not v._lb: + vars_to_update.append(v) + elif ub is not v._ub: + vars_to_update.append(v) elif domain_interval != v.domain.get_interval(): vars_to_update.append(v) self.update_variables(vars_to_update) diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py index 1520407e294..33f6877aaf8 100644 --- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py +++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py @@ -563,8 +563,8 @@ def test_mutable_quadratic_objective( def test_fixed_vars( self, name: str, opt_class: Type[PersistentSolver], only_child_vars ): - opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) for treat_fixed_vars_as_params in [True, False]: + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) opt.update_config.treat_fixed_vars_as_params = treat_fixed_vars_as_params if not opt.available(): raise unittest.SkipTest @@ -1281,6 +1281,34 @@ def test_bug_1(self, name: str, opt_class: Type[PersistentSolver], only_child_va self.assertEqual(res.termination_condition, TerminationCondition.optimal) self.assertAlmostEqual(res.best_feasible_objective, 3) + @parameterized.expand(input=_load_tests(all_solvers, only_child_vars_options)) + def test_bug_2(self, name: str, opt_class: Type[PersistentSolver], only_child_vars): + """ + This test is for a bug where an objective containing a fixed variable does + not get updated properly when the variable is unfixed. + """ + for fixed_var_option in [True, False]: + opt: PersistentSolver = opt_class(only_child_vars=only_child_vars) + if not opt.available(): + raise unittest.SkipTest + opt.update_config.treat_fixed_vars_as_params = fixed_var_option + + m = pe.ConcreteModel() + m.x = pe.Var(bounds=(-10, 10)) + m.y = pe.Var() + m.obj = pe.Objective(expr=3 * m.y - m.x) + m.c = pe.Constraint(expr=m.y >= m.x) + + m.x.fix(1) + res = opt.solve(m) + self.assertAlmostEqual(res.best_feasible_objective, 2, 5) + + m.x.unfix() + m.x.setlb(-9) + m.x.setub(9) + res = opt.solve(m) + self.assertAlmostEqual(res.best_feasible_objective, -18, 5) + @unittest.skipUnless(cmodel_available, 'appsi extensions are not available') class TestLegacySolverInterface(unittest.TestCase):