Skip to content

Commit 0eabe37

Browse files
authored
Merge pull request #252 from POptUS/247NoKwargs
Clean parameterized hfun interface
2 parents 5ca99d4 + 1ba39d3 commit 0eabe37

15 files changed

Lines changed: 489 additions & 125 deletions

docs/pounders.rst

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ subject to
2323
:math:`+\infty` to specify that the problem is unconstrained for the
2424
associated parameter.
2525

26-
Originally, |pounders| was designed only to
27-
minimize a sum of squares of
26+
Originally, |pounders| was designed to
27+
minimize only "sum of squares" mappings of
2828
blackbox functions, that is, |pounders| solved
2929

3030
.. math::
@@ -88,16 +88,28 @@ Python
8888
^^^^^^^^
8989
.. mat:autofunction:: pounders.m.pounders
9090
91-
:math:`\hfun` Functions
92-
^^^^^^^^^^^^^^^^^^^^^^^
93-
The following :math:`\hfun` functions are available for use with both the Python
94-
and |matlab| implementations of |pounders|. While they are presented through
95-
their integration into the Python package, the documentation is valid for the
96-
|matlab| version of these functions, which are located in
91+
General :math:`\hfun` Functions
92+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93+
The following :math:`\hfun` functions are available for immediate use with both
94+
the Python and |matlab| implementations of |pounders|. While they are presented
95+
through their integration into the Python package, the documentation is valid
96+
for the |matlab| version of these functions, which are located in
9797
``pounders/m/general_h_funs``.
9898

9999
.. autofunction:: ibcdfo.pounders.h_leastsquares
100100
.. autofunction:: ibcdfo.pounders.h_neg_leastsquares
101101
.. autofunction:: ibcdfo.pounders.h_identity
102102
.. autofunction:: ibcdfo.pounders.h_emittance
103-
.. autofunction:: ibcdfo.pounders.h_squared_diff_from_mean
103+
104+
Parameterized :math:`\hfun` Functions
105+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
106+
|pounders| specifies parameterized :math:`\hfun` functions, which it can use
107+
only once users have chosen a single set of parameter values for formulating a
108+
specific :math:`\hfun` function and, therefore, a single related problem. The
109+
following routines can be used to create a single ``hfun`` and ``combinemodels``
110+
matched pair for a single set of desired parameter values. While these routines
111+
are presented through their integration into the Python package, the
112+
documentation is valid for the |matlab| version of these routines, which are
113+
located in ``pounders/m/general_h_funs``.
114+
115+
.. autofunction:: ibcdfo.pounders.create_squared_diff_from_mean_functions

docs/python_dev_env.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ The following commands can be run from ``/path/to/IBCDFO/ibcdfo_pypkg``
3333

3434
* ``tox -e pounders``
3535

36-
* Execute the test suite for the |pounders| subpackage only using the code
36+
* Execute the test suite for the |pounders| subpackage using only the code
3737
installed into Python
3838

3939
* ``tox -e manifold_sampling``
4040

41-
* Execute the test suite for the Manifold Sampling subpackage only using the
41+
* Execute the test suite for the Manifold Sampling subpackage using only the
4242
code installed into Python
4343

4444
* ``tox -e report``

goombah/m/tests/compare_goombah_and_pounders.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
combinemodels = @squared_diff_from_mean;
5757
elseif hfun_cases == 3
5858
error("Not implemented in GOOMBAH yet");
59-
if m ~= 3 % Emittance is only defined for the case when m == 3
59+
if m ~= 3 % Emittance is defined only for the case when m == 3
6060
continue
6161
end
6262
hfun = @emittance_h;

manifold_sampling/py/tests/general_smooth_h_funs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def h_leastsquares(z, H0=None):
1111
f(\psp) = \hfun\left(\Ffun(\psp)\right)
1212
= \sum_{i = 1}^{\nd} \Ffuncomp{i}(\psp)^2.
1313
14-
It is intended that this only be used for testing purposes.
14+
It is intended that this be used only for testing purposes.
1515
"""
1616
n = len(z)
1717
h = np.sum(z**2)
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
% From MATLAB and the directory containing this file, execute
2+
% >> runtests
3+
%
4+
% If you would like to run this from a different folder that includes these
5+
% tests as a subfolder, then from that folder execute
6+
% >> runtests("IncludeSubfolders", true)
7+
%
8+
% To execute the test suite with coverage enabled and to generate an HTML-format
9+
% coverage report, execute from /path/to/IBCDFO/pounders/m
10+
% >> runtests("IncludeSubfolders", true, "ReportCoverageFor", pwd)
11+
%
12+
13+
classdef Testcreatesquareddifffrommeanfunctions < matlab.unittest.TestCase
14+
properties
15+
n
16+
m
17+
F
18+
Cres
19+
Gres
20+
Hres
21+
end
22+
23+
methods (TestMethodSetup)
24+
25+
function setup(testCase)
26+
% Test problem 1 setup
27+
% - Here and in tests *_bar refers to an average of *
28+
testCase.n = 2;
29+
testCase.m = 3;
30+
testCase.F = [3.3; -1.1; 4.4];
31+
% F_bar = 2.2;
32+
% F_bar_sqr = 4.84;
33+
% Delta_F = F - F_bar = [1.1; -3.3; 2.2];
34+
% F_sum = 16.94;
35+
36+
testCase.Cres = testCase.F;
37+
38+
testCase.Gres = [[1.1 0.3 2.2]
39+
[2.2 0.3 1.4]];
40+
% G_bar = [1.2 1.3];
41+
42+
testCase.Hres = zeros([testCase.n testCase.n testCase.m]);
43+
testCase.Hres(:, :, 1) = [[1.2 -2.3]
44+
[-2.3 3.4]];
45+
testCase.Hres(:, :, 2) = [[-1.4 3.2]
46+
[3.2 -2.1]];
47+
testCase.Hres(:, :, 3) = [[3.5 1.8]
48+
[1.8 -2.5]];
49+
end
50+
51+
end
52+
53+
methods (Test)
54+
55+
function badArguments(testCase)
56+
testCase.verifyError( ...
57+
@()create_squared_diff_from_mean_functions([]), ...
58+
'MATLAB:validators:mustBeNonempty' ...
59+
);
60+
61+
testCase.verifyError( ...
62+
@()create_squared_diff_from_mean_functions([1.1 2.2]), ...
63+
'MATLAB:validators:mustBeScalarOrEmpty' ...
64+
);
65+
66+
for bad = ["bad", 1j, 1.0 - 2.0 * 1j]
67+
testCase.verifyError( ...
68+
@()create_squared_diff_from_mean_functions(bad), ...
69+
'MATLAB:validators:mustBeReal' ...
70+
);
71+
end
72+
73+
for bad = [inf -inf nan]
74+
testCase.verifyError( ...
75+
@()create_squared_diff_from_mean_functions(bad), ...
76+
'MATLAB:validators:mustBeFinite' ...
77+
);
78+
testCase.verifyError( ...
79+
@()create_squared_diff_from_mean_functions([bad]), ...
80+
'MATLAB:validators:mustBeFinite' ...
81+
);
82+
end
83+
end
84+
85+
function confirmImmutable(testCase)
86+
% Construct using variable declared in this scope & collect results
87+
alpha = 1.2;
88+
[hfun, combinemodels] = ...
89+
create_squared_diff_from_mean_functions(alpha);
90+
hF = hfun(testCase.F);
91+
[G, H] = combinemodels(testCase.Cres, ...
92+
testCase.Gres, ...
93+
testCase.Hres);
94+
95+
% Alter same construction variable & confirm that it yields
96+
% different results
97+
alpha = -2.3 * alpha;
98+
[hfun_2, combinemodels_2] = ...
99+
create_squared_diff_from_mean_functions(alpha);
100+
hF_2 = hfun_2(testCase.F);
101+
[G_2, H_2] = combinemodels_2(testCase.Cres, ...
102+
testCase.Gres, ...
103+
testCase.Hres);
104+
testCase.assertNotEqual(hF, hF_2);
105+
testCase.assertFalse(isequal(G, G_2));
106+
testCase.assertFalse(isequal(H, H_2));
107+
108+
% Confirm that changes to actual alpha argument used to construct
109+
% hfun and combinemodels do not alter those functions. This check
110+
% is motivated by technical subtleties seen with Python.
111+
hF_new = hfun(testCase.F);
112+
[G_new, H_new] = combinemodels(testCase.Cres, ...
113+
testCase.Gres, ...
114+
testCase.Hres);
115+
testCase.assertEqual(hF, hF_new);
116+
testCase.assertTrue(isequal(G, G_new));
117+
testCase.assertTrue(isequal(H, H_new));
118+
end
119+
120+
function testFunctions(testCase)
121+
% Handworked intermediate results for test problem 1
122+
H_bar = [[1.1 0.9]
123+
[0.9 -0.4]];
124+
H_JmGJmG = [[3.64 1.82]
125+
[1.82 3.64]];
126+
H_GG = [[2.88 3.12]
127+
[3.12 3.38]];
128+
H_FH = [[27.28 -18.26]
129+
[-18.26 10.34]];
130+
131+
% Include
132+
% - Negative and positive
133+
% - What look like integer and double literals
134+
% - The special case of alpha = 0.0
135+
for alpha = [-1.1 -5 0.0 2.3 4]
136+
[hfun, combinemodels] = ...
137+
create_squared_diff_from_mean_functions(alpha);
138+
139+
% Check F = F_bar = 0.0 super-duper special case
140+
F = zeros([10 1]);
141+
[G, H] = combinemodels(F, ...
142+
testCase.Gres, ...
143+
testCase.Hres);
144+
testCase.assertEqual(size(G), [testCase.n 1]);
145+
testCase.assertEqual(size(H), [testCase.n testCase.n]);
146+
testCase.assertTrue(isequal(H, H'));
147+
148+
testCase.assertEqual(0.0, hfun(F));
149+
150+
testCase.assertEqual(G, zeros(size(G)));
151+
152+
H_f = H_JmGJmG - alpha * H_GG;
153+
max_abs_err = max(abs(H - H_f), [], "all");
154+
testCase.assertTrue(max_abs_err <= 35.0 * eps);
155+
156+
% Check F - F_bar = 0 with F_bar != 0 special cases
157+
for F_bar = [-10.1 3.3]
158+
F = F_bar * ones([6 1]);
159+
hF = hfun(F);
160+
[G, H] = combinemodels(F, ...
161+
testCase.Gres, ...
162+
testCase.Hres);
163+
testCase.assertEqual(size(G), [testCase.n 1]);
164+
testCase.assertEqual(size(H), [testCase.n testCase.n]);
165+
testCase.assertTrue(isequal(H, H'));
166+
167+
abs_err = abs(hF + alpha * F_bar^2);
168+
testCase.assertEqual(0.0, abs_err);
169+
170+
grad_f = -2.0 * alpha * F_bar * [1.2; 1.3];
171+
abs_err = max(abs(G - grad_f));
172+
testCase.assertTrue(abs_err <= 130.0 * eps);
173+
174+
H_f = H_JmGJmG - alpha * (H_GG + 2.0 * F_bar * H_bar);
175+
max_abs_err = max(abs(H - H_f), [], "all");
176+
testCase.assertTrue(max_abs_err <= 195.0 * eps);
177+
end
178+
179+
% Check generic test problem
180+
hF = hfun(testCase.F);
181+
[G, H] = combinemodels(testCase.Cres, ...
182+
testCase.Gres, ...
183+
testCase.Hres);
184+
testCase.assertEqual(size(G), [testCase.n 1]);
185+
testCase.assertEqual(size(H), [testCase.n testCase.n]);
186+
testCase.assertTrue(isequal(H, H'));
187+
188+
hF_expected = 16.94 - alpha * 4.84;
189+
abs_err = abs(hF - hF_expected);
190+
testCase.assertTrue(abs_err <= 20.0 * eps);
191+
192+
grad_f = [10.12; 9.02] - alpha * [5.28; 5.72];
193+
abs_err = max(abs(G - grad_f));
194+
testCase.assertTrue(abs_err <= 35.0 * eps);
195+
196+
H_f = H_JmGJmG + H_FH - alpha * (H_GG + 2.0 * 2.2 * H_bar);
197+
max_abs_err = max(abs(H - H_f), [], "all");
198+
testCase.assertTrue(max_abs_err <= 70.0 * eps);
199+
end
200+
end
201+
202+
end
203+
end

pounders/m/general_h_funs/combine_squared_diff_from_mean.m

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
function [hfun, combinemodels] = create_squared_diff_from_mean_functions(alpha)
2+
% Please refer to the documentation for the Python version of this
3+
% h function.
4+
5+
% Have MATLAB automatically ensure that each actual alpha argument passed to
6+
% this function is a finite, real scalar.
7+
arguments
8+
alpha {mustBeScalarOrEmpty, mustBeNonempty, mustBeReal, mustBeFinite}
9+
end
10+
11+
function [h] = h_squared_diff_from_mean(F, alpha)
12+
h = sum((F - 1 / length(F) * sum(F)).^2) - alpha * (1 / length(F) * sum(F))^2;
13+
end
14+
15+
function [G, H] = combine_squared_diff_from_mean(Cres, Gres, Hres, alpha)
16+
[n, ~, m] = size(Hres);
17+
18+
m_sumF = mean(Cres);
19+
m_sumG = 1 / m * sum(Gres, 2);
20+
m_sumH = 1 / m * sum(Hres, 3);
21+
22+
G = zeros(n, 1);
23+
for i = 1:m
24+
G = G + (Cres(i) - m_sumF) * (Gres(:, i) - m_sumG);
25+
end
26+
G = 2 * G - 2 * alpha * m_sumF * m_sumG;
27+
28+
H = zeros(n, n);
29+
for i = 1:m
30+
H = H + (Cres(i) - m_sumF) * Hres(:, :, i) + (Gres(:, i) - m_sumG) * (Gres(:, i) - m_sumG)';
31+
end
32+
H = 2 * H;
33+
34+
H = H - (2 * alpha) * (m_sumF * m_sumH + m_sumG * m_sumG');
35+
36+
% [grad, Hess] = matlab_symbolic_grad(Cres,Gres,Hres);
37+
end
38+
39+
hfun = @(F) h_squared_diff_from_mean(F, alpha);
40+
combinemodels = @(Cres, Gres, Hres) combine_squared_diff_from_mean(Cres, Gres, Hres, alpha);
41+
end

pounders/m/general_h_funs/h_squared_diff_from_mean.m

Lines changed: 0 additions & 15 deletions
This file was deleted.

pounders/m/tests/benchmark_pounders.m

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,9 @@
5656
combinemodels = @combine_leastsquares;
5757
elseif hfun_cases == 2
5858
ALPHA = 0;
59-
hfun = @(F) h_squared_diff_from_mean(F, ALPHA);
60-
combinemodels = @(Cres, Gres, Hres) combine_squared_diff_from_mean(Cres, Gres, Hres, ALPHA);
59+
[hfun, combinemodels] = create_squared_diff_from_mean_functions(ALPHA);
6160
elseif hfun_cases == 3
62-
if m ~= 3 % Emittance is only defined for the case when m == 3
61+
if m ~= 3 % Emittance is defined only for the case when m == 3
6362
continue
6463
end
6564
hfun = @h_emittance;

pounders/m/tests/test_bounds_and_sp1.m

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@
6565
combinemodels = @combine_leastsquares;
6666
elseif hfun_cases == 2
6767
ALPHA = 0;
68-
hfun = @(F) h_squared_diff_from_mean(F, ALPHA);
69-
combinemodels = @(Cres, Gres, Hres) combine_squared_diff_from_mean(Cres, Gres, Hres, ALPHA);
68+
[hfun, combinemodels] = create_squared_diff_from_mean_functions(ALPHA);
7069
elseif hfun_cases == 3
7170
hfun = @h_neg_leastsquares;
7271
combinemodels = @combine_neg_leastsquares;

0 commit comments

Comments
 (0)