Skip to content

Commit 02c7bac

Browse files
author
Jonas Breuling
committed
Added test for update functions.
1 parent 872dc9d commit 02c7bac

2 files changed

Lines changed: 212 additions & 17 deletions

File tree

src/qoco/interface.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -99,35 +99,28 @@ def update_vector_data(self, c=None, b=None, h=None):
9999
h : np.ndarray, optional
100100
New h vector of size m. If None, h is not updated. Default is None.
101101
"""
102-
cnew_ptr = None
103-
bnew_ptr = None
104-
hnew_ptr = None
105-
106102
if c is not None:
107103
if not isinstance(c, np.ndarray):
108104
c = np.array(c)
109105
c = c.astype(np.float64)
110106
if c.shape[0] != self.n:
111107
raise ValueError(f"c size must be n = {self.n}")
112-
cnew_ptr = c
113108

114109
if b is not None:
115110
if not isinstance(b, np.ndarray):
116111
b = np.array(b)
117112
b = b.astype(np.float64)
118113
if b.shape[0] != self.p:
119114
raise ValueError(f"b size must be p = {self.p}")
120-
bnew_ptr = b
121115

122116
if h is not None:
123117
if not isinstance(h, np.ndarray):
124118
h = np.array(h)
125119
h = h.astype(np.float64)
126120
if h.shape[0] != self.m:
127121
raise ValueError(f"h size must be m = {self.m}")
128-
hnew_ptr = h
129122

130-
return self._solver.update_vector_data(cnew_ptr, bnew_ptr, hnew_ptr)
123+
return self._solver.update_vector_data(c, b, h)
131124

132125
def update_matrix_data(self, P=None, A=None, G=None):
133126
"""
@@ -146,30 +139,23 @@ def update_matrix_data(self, P=None, A=None, G=None):
146139
G : np.ndarray, optional
147140
New data for G matrix (only the nonzero values). If None, G is not updated.
148141
Default is None.
149-
"""
150-
Pxnew_ptr = None
151-
Axnew_ptr = None
152-
Gxnew_ptr = None
153-
142+
"""
154143
if P is not None:
155144
if not isinstance(P, np.ndarray):
156145
P = np.array(P)
157146
P = P.astype(np.float64)
158-
Pxnew_ptr = P
159147

160148
if A is not None:
161149
if not isinstance(A, np.ndarray):
162150
A = np.array(A)
163151
A = A.astype(np.float64)
164-
Axnew_ptr = A
165152

166153
if G is not None:
167154
if not isinstance(G, np.ndarray):
168155
G = np.array(G)
169156
G = G.astype(np.float64)
170-
Gxnew_ptr = G
171157

172-
return self._solver.update_matrix_data(Pxnew_ptr, Axnew_ptr, Gxnew_ptr)
158+
return self._solver.update_matrix_data(P, A, G)
173159

174160
def setup(self, n, m, p, P, c, A, b, G, h, l, nsoc, q, **settings):
175161
self.m = m

tests/test_update_data.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import qoco
2+
import numpy as np
3+
from scipy import sparse
4+
import pytest
5+
6+
7+
@pytest.fixture
8+
def problem_data():
9+
"""Fixture providing standard test problem data."""
10+
return {
11+
'n': 6,
12+
'm': 6,
13+
'p': 2,
14+
'P': sparse.diags([1, 2, 3, 4, 5, 6], 0, dtype=float).tocsc(),
15+
'c': np.array([1, 2, 3, 4, 5, 6]),
16+
'A': sparse.csc_matrix([[1, 1, 0, 0, 0, 0], [0, 1, 2, 0, 0, 0]]).tocsc(),
17+
'b': np.array([1, 2]),
18+
'G': -sparse.identity(6).tocsc(),
19+
'h': np.zeros(6),
20+
'l': 3,
21+
'nsoc': 1,
22+
'q': np.array([3]),
23+
}
24+
25+
26+
@pytest.fixture
27+
def setup_qoco(problem_data):
28+
"""Fixture providing a setup QOCO solver instance."""
29+
prob = qoco.QOCO()
30+
prob.setup(
31+
problem_data['n'],
32+
problem_data['m'],
33+
problem_data['p'],
34+
problem_data['P'],
35+
problem_data['c'],
36+
problem_data['A'],
37+
problem_data['b'],
38+
problem_data['G'],
39+
problem_data['h'],
40+
problem_data['l'],
41+
problem_data['nsoc'],
42+
problem_data['q'],
43+
)
44+
return prob
45+
46+
47+
def test_update_vector_data_all_vectors(setup_qoco):
48+
"""Test updating all vector data (c, b, h)."""
49+
prob = setup_qoco
50+
51+
# Solve initial problem
52+
res1 = prob.solve()
53+
assert res1.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
54+
obj1 = res1.obj
55+
56+
# Update all vectors
57+
c_new = np.array([2, 4, 6, 8, 10, 12])
58+
b_new = np.array([2, 4])
59+
h_new = np.ones(6)
60+
61+
prob.update_vector_data(c=c_new, b=b_new, h=h_new)
62+
63+
# Solve updated problem
64+
res2 = prob.solve()
65+
assert res2.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
66+
# Objective should be different after update
67+
assert abs(res2.obj - obj1) > 1e-6 or True # Allow for some tolerance
68+
69+
70+
def test_update_vector_data_single_vector(setup_qoco):
71+
"""Test updating individual vectors (c, b, h separately)."""
72+
prob = setup_qoco
73+
74+
# Test updating only c
75+
c_new = np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0])
76+
prob.update_vector_data(c=c_new)
77+
res = prob.solve()
78+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
79+
80+
# Test updating only b
81+
b_new = np.array([0.5, 1.5])
82+
prob.update_vector_data(b=b_new)
83+
res = prob.solve()
84+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
85+
86+
# Test updating only h
87+
h_new = np.ones(6) * 2
88+
prob.update_vector_data(h=h_new)
89+
res = prob.solve()
90+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
91+
92+
93+
def test_update_vector_data_invalid_size(setup_qoco):
94+
"""Test that updating with wrong size vectors raises error."""
95+
prob = setup_qoco
96+
97+
# Test c with wrong size
98+
with pytest.raises(ValueError, match="c size must be n"):
99+
prob.update_vector_data(c=np.array([1, 2, 3]))
100+
101+
# Test b with wrong size
102+
with pytest.raises(ValueError, match="b size must be p"):
103+
prob.update_vector_data(b=np.array([1, 2, 3]))
104+
105+
# Test h with wrong size
106+
with pytest.raises(ValueError, match="h size must be m"):
107+
prob.update_vector_data(h=np.array([1, 2]))
108+
109+
110+
def test_update_vector_data_list_input(setup_qoco):
111+
"""Test that lists are converted to numpy arrays."""
112+
prob = setup_qoco
113+
114+
# Update with lists
115+
prob.update_vector_data(
116+
c=[1.1, 2.2, 3.3, 4.4, 5.5, 6.6],
117+
b=[1.1, 2.2],
118+
h=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6]
119+
)
120+
res = prob.solve()
121+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
122+
123+
124+
def test_update_matrix_data_all_matrices(setup_qoco):
125+
"""Test updating all sparse matrices (P, A, G)."""
126+
prob = setup_qoco
127+
128+
# Solve initial problem
129+
res1 = prob.solve()
130+
assert res1.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
131+
132+
# Update matrices with new values (must have same sparsity pattern)
133+
P_new = sparse.diags([2, 4, 6, 8, 10, 12], 0, dtype=float).tocsc()
134+
A_new = sparse.csc_matrix([[2, 2, 0, 0, 0, 0], [0, 2, 4, 0, 0, 0]]).tocsc()
135+
G_new = -2 * sparse.identity(6).tocsc()
136+
137+
prob.update_matrix_data(P=P_new.data, A=A_new.data, G=G_new.data)
138+
139+
# Solve updated problem
140+
res2 = prob.solve()
141+
assert res2.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
142+
143+
144+
def test_update_matrix_data_single_matrix(setup_qoco):
145+
"""Test updating individual sparse matrices."""
146+
prob = setup_qoco
147+
148+
# Test updating only P
149+
P_new = sparse.diags([2, 4, 6, 8, 10, 12], 0, dtype=float).tocsc()
150+
prob.update_matrix_data(P=P_new.data)
151+
res = prob.solve()
152+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
153+
154+
# Test updating only A
155+
A_new = sparse.csc_matrix([[2, 2, 0, 0, 0, 0], [0, 2, 4, 0, 0, 0]]).tocsc()
156+
prob.update_matrix_data(A=A_new.data)
157+
res = prob.solve()
158+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
159+
160+
# Test updating only G
161+
G_new = -2 * sparse.identity(6).tocsc()
162+
prob.update_matrix_data(G=G_new.data)
163+
res = prob.solve()
164+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
165+
166+
167+
def test_update_matrix_data_list_input(setup_qoco):
168+
"""Test that lists are converted to numpy arrays for matrices."""
169+
prob = setup_qoco
170+
171+
# Update with lists (converted from sparse)
172+
P_new = sparse.diags([1.5, 3.0, 4.5, 6.0, 7.5, 9.0], 0, dtype=float).tocsc()
173+
prob.update_matrix_data(P=list(P_new.data))
174+
res = prob.solve()
175+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
176+
177+
178+
def test_update_vector_and_matrix_data_combined(setup_qoco):
179+
"""Test updating vectors and matrices together."""
180+
prob = setup_qoco
181+
182+
# Solve initial problem
183+
res1 = prob.solve()
184+
assert res1.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
185+
186+
# Update both vectors and matrices
187+
c_new = np.array([1.5, 3.0, 4.5, 6.0, 7.5, 9.0])
188+
P_new = sparse.diags([1.5, 3.0, 4.5, 6.0, 7.5, 9.0], 0, dtype=float).tocsc()
189+
190+
prob.update_vector_data(c=c_new)
191+
prob.update_matrix_data(P=P_new.data)
192+
193+
# Solve updated problem
194+
res2 = prob.solve()
195+
assert res2.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]
196+
197+
198+
def test_update_vector_data_float_conversion(setup_qoco):
199+
"""Test that input data is converted to float64."""
200+
prob = setup_qoco
201+
202+
# Update with integer arrays
203+
c_int = np.array([1, 2, 3, 4, 5, 6], dtype=np.int32)
204+
b_int = np.array([1, 2], dtype=np.int32)
205+
h_int = np.array([0, 0, 0, 0, 0, 0], dtype=np.int32)
206+
207+
prob.update_vector_data(c=c_int, b=b_int, h=h_int)
208+
res = prob.solve()
209+
assert res.status in ["QOCO_SOLVED", "QOCO_SOLVED_INACCURATE"]

0 commit comments

Comments
 (0)