Skip to content

Commit 344b6bb

Browse files
committed
(Issue #291) Add 1D TRSP solver Python test.
This fixes the TRSP solver interface for the outputs. Note that the MINQ wrapper had to be updated to satisfy that interface. As a result, POUNDERS no longer needs to squeeze the solution vector. Tests passing.
1 parent d8dd616 commit 344b6bb

3 files changed

Lines changed: 62 additions & 6 deletions

File tree

pounders/py/create_trsp_solver.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ def create_trsp_solver(spsolver):
4040
of the objective function,
4141
* ``Low`` and ``Upp`` are :math:`\np` element Numpy arrays that specify
4242
the bound constraints,
43-
* ``Xsp`` is the subproblem solution,
43+
* ``Xsp`` is the subproblem solution as a 1D Numpy array,
4444
* ``mdec`` is the value of the subproblem objective function at
45-
the solution, and
45+
the solution as a real scalar, and
4646
* ``found_solution`` is True if a solution was found that should be
4747
acceptable for POUNDERS's purposes; False, otherwise.
4848
"""
@@ -73,6 +73,9 @@ def __minq5_wrapper(H, G, Low, Upp):
7373
# Assume that solver error checks its arguments thoroughly.
7474
n = H.shape[0]
7575
Xsp, mdec, minq_err, _ = minqsw(0, G, H, Low.T, Upp.T, 0, np.zeros((n, 1)))
76+
Xsp = np.atleast_1d(np.squeeze(Xsp))
77+
mdec = float(np.squeeze(mdec))
78+
7679
# Continuous function restricted to (compact) k-cell.
7780
assert minq_err != 1
7881
# TODO: Since we are solving a subproblem, there is likely no sense
@@ -81,7 +84,7 @@ def __minq5_wrapper(H, G, Low, Upp):
8184
# developers/power users to be able to identify when the budget
8285
# limit is reached. Once we have improved logging, print debug
8386
# messages at high verbosity levels if minq_err == 99? Since we are
84-
# returing a boolean, all logging would have to be done by this
87+
# returning a boolean, all logging would have to be done by this
8588
# wrapper layer.
8689
# assert minq_err != 99
8790
return Xsp, mdec, (minq_err >= 0)

pounders/py/pounders.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,11 +260,10 @@ def pounders(Ffun, X_0, n, nf_max, g_tol, delta_0, m, Low, Upp, Prior=None, Opti
260260
Lows = np.maximum(Low - X[xk_in], -delta * np.ones((np.shape(Low))))
261261
Upps = np.minimum(Upp - X[xk_in], delta * np.ones((np.shape(Upp))))
262262
[Xsp, mdec, trsp_flag] = solve_trsp(H, G, Lows, Upps)
263-
if trsp_flag < 0:
263+
if not trsp_flag:
264264
X, F, hF, flag = prepare_outputs_before_return(X, F, hF, nf, -4)
265265
return X, F, hF, flag, xk_in
266266

267-
Xsp = Xsp.squeeze()
268267
step_norm = np.linalg.norm(Xsp, np.inf) if n > 1 else np.abs(Xsp)
269268

270269
# 4. Evaluate the function at the new point (provided the model is

pounders/py/tests/TestCreateTrspSolver.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
Unit test of create_trsp_solver()
33
"""
44

5+
import numbers
56
import warnings
67
import unittest
78

9+
import numpy as np
10+
811
import ibcdfo
912

1013

@@ -27,7 +30,28 @@ def testWarnings(self):
2730
for each in words:
2831
self.assertTrue(each in str(w[0].message))
2932

30-
def testSuccessful(self):
33+
def test1D(self):
34+
EPS = np.finfo(float).eps
35+
36+
# Specify problem
37+
G = np.array([-1.1])
38+
H = np.atleast_2d([2.2])
39+
Low = np.array([-1.9])
40+
Upp = np.array([0.9])
41+
self.assertTrue(H[0] > 0.0)
42+
43+
# Known solutions
44+
s_expected = 0.5
45+
f_expected = -0.275
46+
47+
too_small = np.array([0.25])
48+
s_small = too_small[0]
49+
f_small = -0.20625
50+
51+
too_large = np.array([0.8])
52+
s_large = too_large[0]
53+
f_large = -0.176
54+
3155
for idx in self.__solvers:
3256
# Expected emission of warnings tested in testWarnings. Ignore only those.
3357
warnings.simplefilter("default")
@@ -38,3 +62,33 @@ def testSuccessful(self):
3862

3963
solve_trsp = ibcdfo.pounders.create_trsp_solver(idx)
4064
self.assertTrue(callable(solve_trsp))
65+
66+
# Unconstrained solution in bounds
67+
s_0, f_0, was_successful = solve_trsp(H, G, Low, Upp)
68+
self.assertTrue(was_successful)
69+
self.assertTrue(isinstance(s_0, np.ndarray))
70+
self.assertEqual(s_0.ndim, 1)
71+
self.assertEqual(len(s_0), 1)
72+
self.assertTrue(isinstance(f_0, numbers.Real))
73+
self.assertTrue(np.fabs(1.0 - s_0[0] / s_expected) <= 110.0 * EPS)
74+
self.assertTrue(np.fabs(1.0 - f_0 / f_expected) <= 110.0 * EPS)
75+
76+
# Unconstrained solution outside bounds
77+
s_0, f_0, was_successful = solve_trsp(H, G, Low, too_small)
78+
self.assertTrue(was_successful)
79+
self.assertTrue(isinstance(s_0, np.ndarray))
80+
self.assertEqual(s_0.ndim, 1)
81+
self.assertEqual(len(s_0), 1)
82+
self.assertTrue(isinstance(f_0, numbers.Real))
83+
self.assertTrue(np.fabs(1.0 - s_0[0] / s_small) <= 110.0 * EPS)
84+
self.assertTrue(np.fabs(1.0 - f_0 / f_small) <= 110.0 * EPS)
85+
86+
if idx != ibcdfo.pounders.constants.TRSP_SOLVER_SIMPLE:
87+
# The simple sampler requires that Low <= 0 <= Upp
88+
s_0, f_0, was_successful = solve_trsp(H, G, too_large, Upp)
89+
self.assertTrue(was_successful)
90+
self.assertTrue(isinstance(s_0, np.ndarray))
91+
self.assertEqual(s_0.ndim, 1)
92+
self.assertTrue(isinstance(f_0, numbers.Real))
93+
self.assertTrue(np.fabs(1.0 - s_0 / s_large) <= 110.0 * EPS)
94+
self.assertTrue(np.fabs(1.0 - f_0 / f_large) <= 1500.0 * EPS)

0 commit comments

Comments
 (0)