Skip to content

add qp support for highs #3531

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
39f4392
add qp support for highs
quantresearch1 Mar 20, 2025
823d882
wrong result in test qp
quantresearch1 Mar 25, 2025
2621b3e
pass qp tests
quantresearch1 Mar 25, 2025
0815a03
rename to quadratic_coefs for consistency with gurobi
quantresearch1 Mar 25, 2025
9bba7d0
rename to quadratic_coefs for consistency with gurobi 2
quantresearch1 Mar 25, 2025
1b42058
introduce _MutableObjective
quantresearch1 Mar 25, 2025
c659ccc
fix persistency delta bug
quantresearch1 Mar 25, 2025
38a0a85
or polynomial degree > 2
quantresearch1 Mar 25, 2025
b289dac
Merge branch 'main' into feature/3381_quadratic_obj_highs
quantresearch1 Mar 25, 2025
6c4b7b1
remove changes to legacy appsi_highs
quantresearch1 Mar 25, 2025
891483e
add back mutable vs non mutable handling
quantresearch1 Mar 26, 2025
6d6c2d2
fix more unit tests
quantresearch1 Mar 27, 2025
4a92411
Merge branch 'main' into feature/3381_quadratic_obj_highs
quantresearch1 Mar 28, 2025
8902562
Merge branch 'main' into feature/3381_quadratic_obj_highs
mrmundt Apr 8, 2025
720a5ed
Merge branch 'main' into feature/3381_quadratic_obj_highs
mrmundt Apr 9, 2025
3d71b39
standardize pyomo.environ
quantresearch1 Apr 11, 2025
04eb847
rename q_value to hessian_value
quantresearch1 Apr 14, 2025
9234f32
Merge branch 'main' into feature/3381_quadratic_obj_highs
quantresearch1 Apr 14, 2025
f9fa55a
Merge branch 'main' into feature/3381_quadratic_obj_highs
quantresearch1 May 7, 2025
249fee0
Merge branch 'main' into feature/3381_quadratic_obj_highs
quantresearch1 May 14, 2025
203c6a9
Merge branch 'main' into feature/3381_quadratic_obj_highs
mrmundt May 20, 2025
3f7c74f
Merge branch 'main' into feature/3381_quadratic_obj_highs
quantresearch1 Jun 24, 2025
c1422b7
move qp tests to test_solvers
quantresearch1 Jun 24, 2025
be02bba
delete variable x in test
quantresearch1 Jun 24, 2025
5823583
addressing michael's comments MutableQuadraticCoefficient
quantresearch1 Jun 24, 2025
3fb1476
remove commented bit in test
quantresearch1 Jun 24, 2025
4489372
Merge branch 'main' into feature/3381_quadratic_obj_highs
mrmundt Jun 30, 2025
83784fc
Merge branch 'main' into feature/3381_quadratic_obj_highs
mrmundt Jul 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyomo/contrib/appsi/solvers/highs.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ def _set_objective(self, obj):
)

repn = generate_standard_repn(
obj.expr, quadratic=False, compute_values=False
obj.expr, quadratic=True, compute_values=False
)
if repn.nonlinear_expr is not None:
raise DegreeError(
Expand Down
91 changes: 54 additions & 37 deletions pyomo/contrib/appsi/solvers/tests/test_highs_persistent.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
import sys

import pyomo.common.unittest as unittest
import pyomo.environ as pe
import pyomo.environ as pyo

from pyomo.common.log import LoggingIntercept
from pyomo.common.tee import capture_output
from pyomo.contrib.appsi.solvers.highs import Highs
from pyomo.contrib.appsi.base import TerminationCondition


opt = Highs()
Expand All @@ -28,16 +27,16 @@

class TestBugs(unittest.TestCase):
def test_mutable_params_with_remove_cons(self):
m = pe.ConcreteModel()
m.x = pe.Var(bounds=(-10, 10))
m.y = pe.Var()
m = pyo.ConcreteModel()
m.x = pyo.Var(bounds=(-10, 10))
m.y = pyo.Var()

m.p1 = pe.Param(mutable=True)
m.p2 = pe.Param(mutable=True)
m.p1 = pyo.Param(mutable=True)
m.p2 = pyo.Param(mutable=True)

m.obj = pe.Objective(expr=m.y)
m.c1 = pe.Constraint(expr=m.y >= m.x + m.p1)
m.c2 = pe.Constraint(expr=m.y >= -m.x + m.p2)
m.obj = pyo.Objective(expr=m.y)
m.c1 = pyo.Constraint(expr=m.y >= m.x + m.p1)
m.c2 = pyo.Constraint(expr=m.y >= -m.x + m.p2)

m.p1.value = 1
m.p2.value = 1
Expand All @@ -52,19 +51,19 @@ def test_mutable_params_with_remove_cons(self):
self.assertAlmostEqual(res.best_feasible_objective, -8)

def test_mutable_params_with_remove_vars(self):
m = pe.ConcreteModel()
m.x = pe.Var()
m.y = pe.Var()
m = pyo.ConcreteModel()
m.x = pyo.Var()
m.y = pyo.Var()

m.p1 = pe.Param(mutable=True)
m.p2 = pe.Param(mutable=True)
m.p1 = pyo.Param(mutable=True)
m.p2 = pyo.Param(mutable=True)

m.y.setlb(m.p1)
m.y.setub(m.p2)

m.obj = pe.Objective(expr=m.y)
m.c1 = pe.Constraint(expr=m.y >= m.x + 1)
m.c2 = pe.Constraint(expr=m.y >= -m.x + 1)
m.obj = pyo.Objective(expr=m.y)
m.c1 = pyo.Constraint(expr=m.y >= m.x + 1)
m.c2 = pyo.Constraint(expr=m.y >= -m.x + 1)

m.p1.value = -10
m.p2.value = 10
Expand All @@ -83,16 +82,16 @@ def test_mutable_params_with_remove_vars(self):
def test_fix_and_unfix(self):
# Tests issue https://github.com/Pyomo/pyomo/issues/3127

m = pe.ConcreteModel()
m.x = pe.Var(domain=pe.Binary)
m.y = pe.Var(domain=pe.Binary)
m.fx = pe.Var(domain=pe.NonNegativeReals)
m.fy = pe.Var(domain=pe.NonNegativeReals)
m.c1 = pe.Constraint(expr=m.fx <= m.x)
m.c2 = pe.Constraint(expr=m.fy <= m.y)
m.c3 = pe.Constraint(expr=m.x + m.y <= 1)
m = pyo.ConcreteModel()
m.x = pyo.Var(domain=pyo.Binary)
m.y = pyo.Var(domain=pyo.Binary)
m.fx = pyo.Var(domain=pyo.NonNegativeReals)
m.fy = pyo.Var(domain=pyo.NonNegativeReals)
m.c1 = pyo.Constraint(expr=m.fx <= m.x)
m.c2 = pyo.Constraint(expr=m.fy <= m.y)
m.c3 = pyo.Constraint(expr=m.x + m.y <= 1)

m.obj = pe.Objective(expr=m.fx * 0.5 + m.fy * 0.4, sense=pe.maximize)
m.obj = pyo.Objective(expr=m.fx * 0.5 + m.fy * 0.4, sense=pyo.maximize)

opt = Highs()

Expand Down Expand Up @@ -157,21 +156,21 @@ def test_capture_highs_output(self):
self.assertEqual(ref, OUT.getvalue()[-len(ref) :])

def test_warm_start(self):
m = pe.ConcreteModel()
m = pyo.ConcreteModel()

# decision variables
m.x1 = pe.Var(domain=pe.Integers, name="x1", bounds=(0, 10))
m.x2 = pe.Var(domain=pe.Reals, name="x2", bounds=(0, 10))
m.x3 = pe.Var(domain=pe.Binary, name="x3")
m.x1 = pyo.Var(domain=pyo.Integers, name="x1", bounds=(0, 10))
m.x2 = pyo.Var(domain=pyo.Reals, name="x2", bounds=(0, 10))
m.x3 = pyo.Var(domain=pyo.Binary, name="x3")

# objective function
m.OBJ = pe.Objective(expr=(3 * m.x1 + 2 * m.x2 + 4 * m.x3), sense=pe.maximize)
m.OBJ = pyo.Objective(expr=(3 * m.x1 + 2 * m.x2 + 4 * m.x3), sense=pyo.maximize)

# constraints
m.C1 = pe.Constraint(expr=m.x1 + m.x2 <= 9)
m.C2 = pe.Constraint(expr=3 * m.x1 + m.x2 <= 18)
m.C3 = pe.Constraint(expr=m.x1 <= 7)
m.C4 = pe.Constraint(expr=m.x2 <= 6)
m.C1 = pyo.Constraint(expr=m.x1 + m.x2 <= 9)
m.C2 = pyo.Constraint(expr=3 * m.x1 + m.x2 <= 18)
m.C3 = pyo.Constraint(expr=m.x1 <= 7)
m.C4 = pyo.Constraint(expr=m.x2 <= 6)

# MIP start
m.x1 = 4
Expand All @@ -180,6 +179,24 @@ def test_warm_start(self):

# solving process
with capture_output() as output:
pe.SolverFactory("appsi_highs").solve(m, tee=True, warmstart=True)
pyo.SolverFactory("appsi_highs").solve(m, tee=True, warmstart=True)
log = output.getvalue()
self.assertIn("MIP start solution is feasible, objective value is 25", log)

def test_qp(self):
# test issue #3381
m = pyo.ConcreteModel()

m.x1 = pyo.Var(name='x1', domain=pyo.Reals)
m.x2 = pyo.Var(name='x2', domain=pyo.Reals)

# Quadratic Objective function
m.obj = pyo.Objective(expr=m.x1 * m.x1 + m.x2 * m.x2, sense=pyo.minimize)

m.con1 = pyo.Constraint(expr=m.x1 >= 1)
m.con2 = pyo.Constraint(expr=m.x2 >= 1)

opt = Highs()
opt.solve(m)
self.assertAlmostEqual(m.x1.value, 1, places=5)
self.assertAlmostEqual(m.x2.value, 1, places=5)
Loading