Skip to content

Commit 26c5bd8

Browse files
committed
Make Highs support two sided linear constraint
1 parent e4b42c3 commit 26c5bd8

File tree

5 files changed

+72
-20
lines changed

5 files changed

+72
-20
lines changed

include/pyoptinterface/highs_model.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ struct POIHighsSolution
112112
};
113113

114114
class POIHighsModel : public OnesideLinearConstraintMixin<POIHighsModel>,
115+
public TwosideLinearConstraintMixin<POIHighsModel>,
115116
public LinearObjectiveMixin<POIHighsModel>,
116117
public PPrintMixin<POIHighsModel>,
117118
public GetValueMixin<POIHighsModel>
@@ -136,6 +137,9 @@ class POIHighsModel : public OnesideLinearConstraintMixin<POIHighsModel>,
136137
ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function,
137138
ConstraintSense sense, CoeffT rhs,
138139
const char *name = nullptr);
140+
ConstraintIndex add_linear_constraint(const ScalarAffineFunction &function,
141+
const std::tuple<double, double> &interval,
142+
const char *name = nullptr);
139143
ConstraintIndex add_quadratic_constraint(const ScalarQuadraticFunction &function,
140144
ConstraintSense sense, CoeffT rhs,
141145
const char *name = nullptr);

lib/highs_model.cpp

+29-14
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,30 @@ void POIHighsModel::set_variable_bounds(const VariableIndex &variable, double lb
257257
ConstraintIndex POIHighsModel::add_linear_constraint(const ScalarAffineFunction &function,
258258
ConstraintSense sense, CoeffT rhs,
259259
const char *name)
260+
{
261+
double lb = -kHighsInf;
262+
double ub = kHighsInf;
263+
switch (sense)
264+
{
265+
case ConstraintSense::LessEqual:
266+
ub = rhs;
267+
break;
268+
case ConstraintSense::GreaterEqual:
269+
lb = rhs;
270+
break;
271+
case ConstraintSense::Equal:
272+
lb = rhs;
273+
ub = rhs;
274+
break;
275+
}
276+
277+
auto con = add_linear_constraint(function, std::make_tuple(lb, ub), name);
278+
return con;
279+
}
280+
281+
ConstraintIndex POIHighsModel::add_linear_constraint(const ScalarAffineFunction &function,
282+
const std::tuple<double, double> &interval,
283+
const char *name)
260284
{
261285
IndexT index = m_linear_constraint_index.add_index();
262286
ConstraintIndex constraint(ConstraintType::Linear, index);
@@ -268,21 +292,12 @@ ConstraintIndex POIHighsModel::add_linear_constraint(const ScalarAffineFunction
268292
HighsInt *cind = ptr_form.index;
269293
double *cval = ptr_form.value;
270294

271-
double lb = -kHighsInf;
272-
double ub = kHighsInf;
273-
double g_rhs = rhs - function.constant.value_or(0.0);
274-
switch (sense)
295+
double lb = std::get<0>(interval);
296+
double ub = std::get<1>(interval);
297+
if (function.constant.has_value())
275298
{
276-
case ConstraintSense::LessEqual:
277-
ub = g_rhs;
278-
break;
279-
case ConstraintSense::GreaterEqual:
280-
lb = g_rhs;
281-
break;
282-
case ConstraintSense::Equal:
283-
lb = g_rhs;
284-
ub = g_rhs;
285-
break;
299+
lb -= function.constant.value();
300+
ub -= function.constant.value();
286301
}
287302

288303
auto error = highs::Highs_addRow(m_model.get(), lb, ub, numnz, cind, cval);

lib/highs_model_ext.cpp

+15-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include <nanobind/nanobind.h>
22
#include <nanobind/stl/string.h>
3+
#include <nanobind/stl/tuple.h>
34
#include <nanobind/stl/vector.h>
45

56
#include "pyoptinterface/highs_model.hpp"
@@ -78,12 +79,23 @@ NB_MODULE(highs_model_ext, m)
7879
nb::overload_cast<const ExprBuilder &, int>(&HighsModel::pprint_expression),
7980
nb::arg("expr"), nb::arg("precision") = 4)
8081

81-
.def("_add_linear_constraint", &HighsModel::add_linear_constraint, nb::arg("expr"),
82-
nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "")
83-
.def("_add_linear_constraint", &HighsModel::add_linear_constraint_from_var,
82+
.def("_add_linear_constraint",
83+
nb::overload_cast<const ScalarAffineFunction &, ConstraintSense, CoeffT, const char *>(
84+
&HighsModel::add_linear_constraint),
8485
nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "")
86+
.def("_add_linear_constraint",
87+
nb::overload_cast<const ScalarAffineFunction &, const std::tuple<double, double> &,
88+
const char *>(&HighsModel::add_linear_constraint),
89+
nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "")
90+
.def("_add_linear_constraint", &HighsModel::add_linear_constraint_from_var, nb::arg("expr"),
91+
nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "")
92+
.def("_add_linear_constraint", &HighsModel::add_linear_interval_constraint_from_var,
93+
nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "")
8594
.def("_add_linear_constraint", &HighsModel::add_linear_constraint_from_expr,
8695
nb::arg("expr"), nb::arg("sense"), nb::arg("rhs"), nb::arg("name") = "")
96+
.def("_add_linear_constraint", &HighsModel::add_linear_interval_constraint_from_expr,
97+
nb::arg("expr"), nb::arg("interval"), nb::arg("name") = "")
98+
8799
.def("delete_constraint", &HighsModel::delete_constraint)
88100
.def("is_constraint_active", &HighsModel::is_constraint_active)
89101

lib/mosek_model.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -1484,7 +1484,8 @@ void MOSEKEnv::close()
14841484
{
14851485
if (m_env != nullptr)
14861486
{
1487-
mosek::MSK_deleteenv(&m_env);
1487+
auto error = mosek::MSK_deleteenv(&m_env);
1488+
check_error(error);
14881489
}
14891490
m_env = nullptr;
14901491
}

tests/test_in_constraint.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import pyoptinterface as poi
2+
from pyoptinterface import nl, gurobi
23

34
import pytest
45

56

6-
def test_in_constraint_sense(nlp_model_ctor):
7-
model = nlp_model_ctor()
7+
def test_linear_in_constraint(model_interface):
8+
model = model_interface
9+
10+
if isinstance(model, gurobi.Model):
11+
pytest.skip("Gurobi does not support range linear constraints")
812

913
x = model.add_variable(lb=0.0)
1014
y = model.add_variable(lb=0.0)
@@ -22,3 +26,19 @@ def test_in_constraint_sense(nlp_model_ctor):
2226

2327
expr_value = model.get_value(expr)
2428
assert expr_value == pytest.approx(1.0, rel=1e-6)
29+
30+
31+
def test_nl_in_constraint(nlp_model_ctor):
32+
model = nlp_model_ctor()
33+
34+
x = model.add_variable(lb=0.0)
35+
y = model.add_variable(lb=0.0)
36+
37+
with nl.graph():
38+
expr = nl.exp(x) + nl.exp(y)
39+
model.add_nl_constraint(expr, (5.0, 10.0))
40+
model.add_nl_objective(expr)
41+
42+
model.optimize()
43+
objective_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue)
44+
assert objective_value == pytest.approx(5.0, abs=1e-6)

0 commit comments

Comments
 (0)