Skip to content

Commit 92e65d3

Browse files
authored
Merge pull request #1591 from Libensemble/updating_ibcdfo_example
Adding test for ibcdfo with jax
2 parents ad42858 + 756a854 commit 92e65d3

8 files changed

Lines changed: 381 additions & 8 deletions

install/install_ibcdfo.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22

3-
git clone --recurse-submodules -b develop https://github.com/POptUS/IBCDFO.git
3+
git clone --recurse-submodules -b updates_manifold_sampling https://github.com/POptUS/IBCDFO.git
44
pushd IBCDFO/minq/py/minq5/
55
export PYTHONPATH="$PYTHONPATH:$(pwd)"
66
echo "PYTHONPATH=$PYTHONPATH" >> $GITHUB_ENV

libensemble/gen_funcs/aposmm_localopt_support.py

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"run_local_tao",
1010
"run_local_dfols",
1111
"run_local_ibcdfo_pounders",
12+
"run_local_ibcdfo_manifold_sampling",
1213
"run_local_scipy_opt",
1314
"run_external_localopt",
1415
]
@@ -26,7 +27,7 @@ class APOSMMException(Exception):
2627
"""Raised for any exception in APOSMM"""
2728

2829

29-
optimizer_list = ["petsc", "nlopt", "dfols", "scipy", "ibcdfo", "external"]
30+
optimizer_list = ["petsc", "nlopt", "dfols", "scipy", "ibcdfo_pounders", "ibcdfo_manifold_sampling", "external"]
3031
optimizers = libensemble.gen_funcs.rc.aposmm_optimizers
3132

3233
if optimizers is not None:
@@ -42,8 +43,10 @@ class APOSMMException(Exception):
4243
import nlopt # noqa: F401
4344
if "dfols" in optimizers:
4445
import dfols # noqa: F401
45-
if "ibcdfo" in optimizers:
46-
from ibcdfo import pounders # noqa: F401
46+
if "ibcdfo_pounders" in optimizers:
47+
from ibcdfo.pounders import pounders # noqa: F401
48+
if "ibcdfo_manifold_sampling" in optimizers:
49+
from ibcdfo.manifold_sampling import manifold_sampling_primal # noqa: F401
4750
if "scipy" in optimizers:
4851
from scipy import optimize as sp_opt # noqa: F401
4952
if "external_localopt" in optimizers:
@@ -79,6 +82,7 @@ class LocalOptInterfacer(object):
7982
- PETSc/TAO [``'pounders'``, ``'blmvm'``, ``'nm'``]
8083
- SciPy [``'scipy_Nelder-Mead'``, ``'scipy_COBYLA'``, ``'scipy_BFGS'``]
8184
- DFOLS [``'dfols'``]
85+
- IBCDFO [``'pounders'``, ``'manifold_sampling_primal'``]
8286
- External local optimizer [``'external_localopt'``] (which use files to pass/receive ``x/f`` values)
8387
"""
8488

@@ -123,6 +127,8 @@ def __init__(self, user_specs, x0, f0, grad0=None):
123127
run_local_opt = run_local_dfols
124128
elif user_specs["localopt_method"] in ["ibcdfo_pounders"]:
125129
run_local_opt = run_local_ibcdfo_pounders
130+
elif user_specs["localopt_method"] in ["ibcdfo_manifold_sampling"]:
131+
run_local_opt = run_local_ibcdfo_manifold_sampling
126132
elif user_specs["localopt_method"] in ["external_localopt"]:
127133
run_local_opt = run_external_localopt
128134
else:
@@ -417,6 +423,60 @@ def run_local_dfols(user_specs, comm_queue, x0, f0, child_can_read, parent_can_r
417423
finish_queue(x_opt, opt_flag, comm_queue, parent_can_read, user_specs)
418424

419425

426+
def run_local_ibcdfo_manifold_sampling(user_specs, comm_queue, x0, f0, child_can_read, parent_can_read):
427+
"""
428+
Runs a IBCDFO local optimization run starting at ``x0``, governed by the
429+
parameters in ``user_specs``.
430+
431+
Although IBCDFO methods can receive previous evaluations, few other methods
432+
support that, so APOSMM assumes the first point will be re-evaluated (but
433+
not be sent back to the manager).
434+
"""
435+
n = len(x0)
436+
# Define bound constraints (lower <= x <= upper)
437+
lb = np.zeros(n)
438+
ub = np.ones(n)
439+
440+
# Set random seed (for reproducibility)
441+
np.random.seed(0)
442+
443+
# dist_to_bound = min(min(ub - x0), min(x0 - lb))
444+
# assert dist_to_bound > np.finfo(np.float64).eps, "The distance to the boundary is too small"
445+
446+
run_max_eval = user_specs.get("run_max_eval", 100 * (n + 1))
447+
# g_tol = 1e-8
448+
# delta_0 = 0.5 * dist_to_bound
449+
# m = len(f0)
450+
subprob_switch = "linprog"
451+
452+
[X, F, hF, xkin, flag] = manifold_sampling_primal(
453+
user_specs["hfun"],
454+
lambda x: scipy_dfols_callback_fun(x, comm_queue, child_can_read, parent_can_read, user_specs),
455+
x0,
456+
lb,
457+
ub,
458+
run_max_eval,
459+
subprob_switch,
460+
)
461+
462+
assert flag >= 0 or flag == -6, "IBCDFO errored"
463+
464+
x_opt = X[xkin]
465+
466+
if flag > 0:
467+
opt_flag = 1
468+
else:
469+
print(
470+
"[APOSMM] The IBCDFO run started from " + str(x0) + " stopped with an exit "
471+
"flag of " + str(flag) + ". No point from this run will be "
472+
"ruled as a minimum! APOSMM may start a new run from some point "
473+
"in this run."
474+
)
475+
opt_flag = 0
476+
477+
finish_queue(x_opt, opt_flag, comm_queue, parent_can_read, user_specs)
478+
479+
420480
def run_local_ibcdfo_pounders(user_specs, comm_queue, x0, f0, child_can_read, parent_can_read):
421481
"""
422482
Runs a IBCDFO local optimization run starting at ``x0``, governed by the
@@ -447,7 +507,7 @@ def run_local_ibcdfo_pounders(user_specs, comm_queue, x0, f0, child_can_read, pa
447507
else:
448508
Options = None
449509

450-
[X, F, hF, flag, xkin] = pounders.pounders(
510+
[X, F, hF, flag, xkin] = pounders(
451511
lambda x: scipy_dfols_callback_fun(x, comm_queue, child_can_read, parent_can_read, user_specs),
452512
x0,
453513
n,

libensemble/gen_funcs/persistent_aposmm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ def initialize_children(user_specs):
739739
"nm",
740740
]:
741741
fields_to_pass = ["x_on_cube", "f"]
742-
elif user_specs["localopt_method"] in ["pounders", "ibcdfo_pounders", "dfols"]:
742+
elif user_specs["localopt_method"] in ["pounders", "ibcdfo_pounders", "ibcdfo_manifold_sampling", "dfols"]:
743743
fields_to_pass = ["x_on_cube", "fvec"]
744744
else:
745745
raise NotImplementedError(f"Unknown local optimization method {user_specs['localopt_method']}.")
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# This declares the hfun for Test_compare_pounder_pounders_with_jax.py and
2+
# then used jax to combine the quadratic models of each component of the
3+
# inputs to the hfun.
4+
#
5+
# For other general use cases of pounders on smooth hfuns, only the hfun below
6+
# needs to be changed (and combinemodels_jax can be given to pounders)
7+
8+
9+
import jax
10+
import numpy
11+
12+
jax.config.update("jax_enable_x64", True)
13+
14+
15+
def hfun(z):
16+
res = z[0] * z[1] - z[2] ** 2
17+
return res
18+
19+
20+
@jax.jit
21+
def hfun_d(z, zd):
22+
resd = jax.jvp(hfun, (z,), (zd,))
23+
return resd
24+
25+
26+
@jax.jit
27+
def hfun_dd(z, zd, zdt, zdd):
28+
_, resdd = jax.jvp(hfun_d, (z, zd), (zdt, zdd))
29+
return resdd
30+
31+
32+
def G_combine(Cres, Gres):
33+
n, m = Gres.shape
34+
G = numpy.zeros(n)
35+
for i in range(n):
36+
_, G[i] = hfun_d(Cres, Gres[i, :])
37+
return G
38+
39+
40+
def H_combine(Cres, Gres, Hres):
41+
n, _, m = Hres.shape
42+
H = numpy.zeros((n, n))
43+
for i in range(n):
44+
for j in range(n):
45+
_, H[i, j] = hfun_dd(Cres, Gres[i, :], Gres[j, :], Hres[i, j, :])
46+
return H
47+
48+
49+
def combinemodels_jax(Cres, Gres, Hres):
50+
return G_combine(Cres, Gres), H_combine(Cres, Gres, Hres)
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
"""
2+
Runs libEnsemble with APOSMM+IBCDFO on two test problems. Only a single
3+
optimization run is being performed for the below setup.
4+
5+
The first case uses POUNDERS to solve the chwirut least-squares problem. For
6+
this case, all chwirut 214 residual calculations for a given point are
7+
performed as a single simulation evaluation.
8+
9+
The second case uses the generalized POUNDERS to minimize normalized beamline
10+
emittance. The "beamline simulation" is a synthetic polynomial test function
11+
that takes in 4 variables and returning 3 outputs. These outputs represent
12+
position <x>, momentum <p_x>, and the correlation between them <x p_x>.
13+
14+
These values are then mapped to the normalized emittance <x> <p_x> - <x p_x>.
15+
16+
Execute via one of the following commands:
17+
mpiexec -np 3 python test_persistent_aposmm_ibcdfo_pounders.py
18+
python test_persistent_aposmm_ibcdfo_pounders.py --nworkers 2
19+
Both will run with 1 manager, 1 worker running APOSMM+IBCDFO, and 1 worker
20+
doing the simulation evaluations.
21+
"""
22+
23+
# Do not change these lines - they are parsed by run-tests.sh
24+
# TESTSUITE_COMMS: local mpi
25+
# TESTSUITE_NPROCS: 3
26+
27+
import multiprocessing
28+
import sys
29+
30+
import numpy as np
31+
32+
import libensemble.gen_funcs
33+
from libensemble.libE import libE
34+
35+
libensemble.gen_funcs.rc.aposmm_optimizers = "ibcdfo_manifold_sampling"
36+
37+
from libensemble.alloc_funcs.persistent_aposmm_alloc import persistent_aposmm_alloc as alloc_f
38+
from libensemble.gen_funcs.persistent_aposmm import aposmm as gen_f
39+
from libensemble.tools import add_unique_random_streams, parse_args, save_libE_output
40+
41+
try:
42+
from ibcdfo.manifold_sampling import manifold_sampling_primal # noqa: F401
43+
from ibcdfo.manifold_sampling.h_examples import pw_maximum as hfun
44+
45+
except ModuleNotFoundError:
46+
sys.exit("Please 'pip install ibcdfo'")
47+
48+
try:
49+
from minqsw import minqsw # noqa: F401
50+
51+
except ModuleNotFoundError:
52+
sys.exit("Ensure https://github.com/POptUS/minq has been cloned and that minq/py/minq5/ is on the PYTHONPATH")
53+
54+
55+
def synthetic_beamline_mapping(H, _, sim_specs):
56+
x = H["x"][0]
57+
assert len(x) == 4, "Assuming 4 inputs to this function"
58+
y = np.zeros(3) # Synthetic beamline outputs
59+
y[0] = x[0] ** 2 + 1.0
60+
y[1] = x[1] ** 2 + 2.0
61+
y[2] = x[2] * x[3] + 0.5
62+
63+
Out = np.zeros(1, dtype=sim_specs["out"])
64+
Out["fvec"] = y
65+
Out["f"] = np.max(y)
66+
return Out
67+
68+
69+
# Main block is necessary only when using local comms with spawn start method (default on macOS and Windows).
70+
if __name__ == "__main__":
71+
multiprocessing.set_start_method("fork", force=True)
72+
73+
nworkers, is_manager, libE_specs, _ = parse_args()
74+
75+
assert nworkers == 2, "This test is just for two workers"
76+
77+
m = 3
78+
n = 4
79+
sim_f = synthetic_beamline_mapping
80+
81+
sim_specs = {
82+
"sim_f": sim_f,
83+
"in": ["x"],
84+
"out": [("f", float), ("fvec", float, m)],
85+
}
86+
87+
gen_out = [
88+
("x", float, n),
89+
("x_on_cube", float, n),
90+
("sim_id", int),
91+
("local_min", bool),
92+
("local_pt", bool),
93+
("started_run", bool),
94+
]
95+
96+
gen_specs = {
97+
"gen_f": gen_f,
98+
"persis_in": ["f", "fvec"] + [n[0] for n in gen_out],
99+
"out": gen_out,
100+
"user": {
101+
"initial_sample_size": 1,
102+
"stop_after_k_runs": 1,
103+
"max_active_runs": 1,
104+
"sample_points": np.atleast_2d(0.1 * (np.arange(n) + 1)),
105+
"localopt_method": "ibcdfo_manifold_sampling",
106+
"run_max_eval": 100 * (n + 1),
107+
"components": m,
108+
"lb": -1 * np.ones(n),
109+
"ub": np.ones(n),
110+
},
111+
}
112+
113+
gen_specs["user"]["hfun"] = hfun
114+
115+
alloc_specs = {"alloc_f": alloc_f}
116+
117+
persis_info = add_unique_random_streams({}, nworkers + 1)
118+
119+
exit_criteria = {"sim_max": 500}
120+
121+
# Perform the run
122+
H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, alloc_specs, libE_specs)
123+
124+
if is_manager:
125+
assert np.min(H["f"]) == 2.0, "The best is 2"
126+
assert persis_info[1].get("run_order"), "Run_order should have been given back"
127+
assert flag == 0
128+
129+
save_libE_output(H, persis_info, __file__, nworkers)

libensemble/tests/regression_tests/test_persistent_aposmm_ibcdfo_pounders.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from libensemble.libE import libE
3434
from libensemble.sim_funcs.chwirut1 import chwirut_eval
3535

36-
libensemble.gen_funcs.rc.aposmm_optimizers = "ibcdfo"
36+
libensemble.gen_funcs.rc.aposmm_optimizers = "ibcdfo_pounders"
3737

3838
from libensemble.alloc_funcs.persistent_aposmm_alloc import persistent_aposmm_alloc as alloc_f
3939
from libensemble.gen_funcs.persistent_aposmm import aposmm as gen_f

0 commit comments

Comments
 (0)