Skip to content

Commit 21113e6

Browse files
committed
Fix Hessian matrix in HiGHS
We should sort the elements in the same column by its row number
1 parent dfd16f3 commit 21113e6

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

include/pyoptinterface/solver_common.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ class OnesideLinearConstraintMixin
4040
{
4141
if (function.degree() >= 2)
4242
{
43-
throw std::runtime_error("add_linear_constraint expects linear expression but receives a quadratic expression.");
43+
throw std::runtime_error("add_linear_constraint expects linear expression but receives "
44+
"a quadratic expression.");
4445
}
4546
ScalarAffineFunction f(function);
4647
return get_base()->add_linear_constraint(f, sense, rhs, name);
@@ -617,7 +618,16 @@ struct CSCMatrix
617618
// Sorting based on column indices
618619
std::vector<IDXT> idx(numnz);
619620
std::iota(idx.begin(), idx.end(), 0);
620-
std::sort(idx.begin(), idx.end(), [&](int i, int j) { return cols[i] < cols[j]; });
621+
std::sort(idx.begin(), idx.end(), [&](int i, int j) {
622+
if (cols[i] == cols[j])
623+
{
624+
return rows[i] < rows[j];
625+
}
626+
else
627+
{
628+
return cols[i] < cols[j];
629+
}
630+
});
621631

622632
// Creating CSC arrays
623633
values_CSC.reserve(numnz);

tests/test_qp.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import pyoptinterface as poi
22
from pytest import approx
33

4+
import numpy as np
5+
46

57
def test_simple_qp(model_interface):
68
model = model_interface
@@ -21,3 +23,46 @@ def test_simple_qp(model_interface):
2123

2224
obj_val = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue)
2325
assert obj_val == approx(N**2)
26+
27+
28+
# reported by https://github.com/metab0t/PyOptInterface/issues/59
29+
def test_shuffle_qp_objective(model_interface):
30+
model = model_interface
31+
32+
N = 3
33+
weights = model.add_m_variables(N, lb=0)
34+
35+
expected_returns = [0.05, 0.07, 0.12]
36+
min_return = 0.06
37+
cov = [
38+
(0, 0, 0.1),
39+
(0, 1, 0.1),
40+
(0, 2, 0.04),
41+
(1, 1, 0.4),
42+
(1, 2, 0.2),
43+
(2, 2, 0.9),
44+
]
45+
cov_objs = [weights[i] * weights[j] * v for i, j, v in cov]
46+
47+
model.add_linear_constraint(poi.quicksum(weights) == 1)
48+
model.add_linear_constraint(
49+
poi.quicksum(expected_returns[i] * weights[i] for i in range(N)) >= min_return
50+
)
51+
52+
trial = 120
53+
obj_values = []
54+
for _ in range(trial):
55+
import random
56+
57+
random.shuffle(cov_objs)
58+
obj = poi.quicksum(cov_objs)
59+
model.set_objective(obj, poi.ObjectiveSense.Minimize)
60+
61+
model.optimize()
62+
63+
obj_value = model.get_model_attribute(poi.ModelAttribute.ObjectiveValue)
64+
obj_values.append(obj_value)
65+
66+
obj_values = np.array(obj_values)
67+
# test all values are the same
68+
assert np.all(np.abs(obj_values - obj_values[0]) < 1e-8)

0 commit comments

Comments
 (0)