Repository: Pyomo/pyomo

Issue:
Inconsistent behavior between the CBC and HiGHS
I'm puzzled by inconsistent behavior between the CBC and HiGHS solvers.

Given the attached model, CBC returns the correct optimal solution of 15, while HiGHS fails with an error: "ValueError: Error when adding rows". The choice of solver is specified on line 3.

The HiGHS error occurs on line 30. However, if we comment out line 30 and uncomment line 31, then HiGHS works correctly.

When using HiGHS, why is it necessary to use pyo.value(Model.ShelfHeights[s]) rather than just Model.ShelfHeights[s] like we can with CBC (and GLPK)?

Pyomo version 6.4.3
HiGHS version 1.4.0

[HiGHS_test.txt](https://github.com/Pyomo/pyomo/files/10156757/HiGHS_test.txt)



Discussion:
@SolverMax it would help us debug this if you could provide the full stacktrace of the error you're seeing. 

I haven't tried running your model but on first glance this seems like a bug, the APPSI HiGHS interface should work on models with mutable parameters without needing to call the `value` function. 

For reference for others these are lines 30 and 31 mentioned above:
```
# Model.ShelfHeights is a mutable indexed Param
def rule_fit(Model, P):
        # line 30
        return sum(Model.ShelfHeights[s] * Model.Allocation[P, s] for s in Model.S) >= Model.Pallets[P]
        # line 31
        #return sum(pyo.value(Model.ShelfHeights[s]) * Model.Allocation[P, s] for s in Model.S) >= Model.Pallets[P]
Model.PalletFits = pyo.Constraint(Model.P, rule = rule_fit)
```

When I run the model, the error message is:
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Input In [7], in <cell line: 1>()
----> 1 Main()

Input In [6], in Main()
      5 DefineData(Model, Pallets, ShelfHeights)
      6 DefineModel(Model)
----> 7 Results = Solver.solve(Model)
      8 print(f'Objective = {pyo.value(Model.Racks()):5,.0f}')

File D:\onedrive\python\blog\lib\site-packages\pyomo\contrib\appsi\base.py:1308, in LegacySolverInterface.solve(self, model, tee, load_solutions, logfile, solnfile, timelimit, report_timing, solver_io, suffixes, options, keepfiles, symbolic_solver_labels)
   1305 if options is not None:
   1306     self.options = options
-> 1308 results: Results = super(LegacySolverInterface, self).solve(model)
   1310 legacy_results = LegacySolverResults()
   1311 legacy_soln = LegacySolution()

File D:\onedrive\python\blog\lib\site-packages\pyomo\contrib\appsi\solvers\highs.py:215, in Highs.solve(self, model, timer)
    213 if model is not self._model:
    214     timer.start('set_instance')
--> 215     self.set_instance(model)
    216     timer.stop('set_instance')
    217 else:

File D:\onedrive\python\blog\lib\site-packages\pyomo\contrib\appsi\solvers\highs.py:320, in Highs.set_instance(self, model)
    317     self._expr_types = cmodel.PyomoExprTypes()
    319 self._solver_model = highspy.Highs()
--> 320 self.add_block(model)
    321 if self._objective is None:
    322     self.set_objective(None)

File D:\onedrive\python\blog\lib\site-packages\pyomo\contrib\appsi\base.py:951, in PersistentBase.add_block(self, block)
    949 if self._only_child_vars:
    950     self.add_variables(list(dict((id(var), var) for var in block.component_data_objects(Var, descend_into=True)).values()))
--> 951 self.add_constraints([con for con in block.component_data_objects(Constraint, descend_into=True,
    952                                                                   active=True)])
    953 self.add_sos_constraints([con for con in block.component_data_objects(SOSConstraint, descend_into=True,
    954                                                                       active=True)])
    955 obj = get_objective(block)

File D:\onedrive\python\blog\lib\site-packages\pyomo\contrib\appsi\base.py:878, in PersistentBase.add_constraints(self, cons)
    876             v.unfix()
    877             all_fixed_vars[id(v)] = v
--> 878 self._add_constraints(cons)
    879 for v in all_fixed_vars.values():
    880     v.fix()

File D:\onedrive\python\blog\lib\site-packages\pyomo\contrib\appsi\solvers\highs.py:382, in Highs._add_constraints(self, cons)
    379     self._solver_con_to_pyomo_con_map[current_num_cons] = con
    380     current_num_cons += 1
--> 382 self._solver_model.addRows(len(lbs),
    383                            np.array(lbs, dtype=np.double),
    384                            np.array(ubs, dtype=np.double),
    385                            len(coef_values),
    386                            np.array(starts),
    387                            np.array(var_indices),
    388                            np.array(coef_values, dtype=np.double))

ValueError: Error when adding rows
```
+1

Resolving patch:
diff --git a/pyomo/contrib/appsi/solvers/highs.py b/pyomo/contrib/appsi/solvers/highs.py
--- a/pyomo/contrib/appsi/solvers/highs.py
+++ b/pyomo/contrib/appsi/solvers/highs.py
@@ -343,6 +343,7 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]):
             for ndx, coef in enumerate(repn.linear_coefs):
                 v = repn.linear_vars[ndx]
                 v_id = id(v)
+                coef_val = value(coef)
                 if not is_constant(coef):
                     mutable_linear_coefficient = _MutableLinearCoefficient(pyomo_con=con, pyomo_var_id=v_id,
                                                                            con_map=self._pyomo_con_to_solver_con_map,
@@ -352,8 +353,10 @@ def _add_constraints(self, cons: List[_GeneralConstraintData]):
                     if con not in self._mutable_helpers:
                         self._mutable_helpers[con] = list()
                     self._mutable_helpers[con].append(mutable_linear_coefficient)
+                    if coef_val == 0:
+                        continue
                 var_indices.append(self._pyomo_var_to_solver_var_map[v_id])
-                coef_values.append(value(coef))
+                coef_values.append(coef_val)
 
             if con.has_lb():
                 lb = con.lower - repn.constant


Test patch:
diff --git a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py
--- a/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py
+++ b/pyomo/contrib/appsi/solvers/tests/test_persistent_solvers.py
@@ -1089,6 +1089,29 @@ def test_variables_elsewhere2(self, name: str, opt_class: Type[PersistentSolver]
         self.assertIn(m.y, sol)
         self.assertNotIn(m.z, sol)
 
+    @parameterized.expand(input=all_solvers)
+    def test_bug_1(self, name: str, opt_class: Type[PersistentSolver]):
+        opt: PersistentSolver = opt_class()
+        if not opt.available():
+            raise unittest.SkipTest
+
+        m = pe.ConcreteModel()
+        m.x = pe.Var(bounds=(3, 7))
+        m.y = pe.Var(bounds=(-10, 10))
+        m.p = pe.Param(mutable=True, initialize=0)
+
+        m.obj = pe.Objective(expr=m.y)
+        m.c = pe.Constraint(expr=m.y >= m.p*m.x)
+
+        res = opt.solve(m)
+        self.assertEqual(res.termination_condition, TerminationCondition.optimal)
+        self.assertAlmostEqual(res.best_feasible_objective, 0)
+
+        m.p.value = 1
+        res = opt.solve(m)
+        self.assertEqual(res.termination_condition, TerminationCondition.optimal)
+        self.assertAlmostEqual(res.best_feasible_objective, 3)
+
 
 @unittest.skipUnless(cmodel_available, 'appsi extensions are not available')
 class TestLegacySolverInterface(unittest.TestCase):