Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
c8d6a82
starting to populate the APOSMM class with common kwargs, for better …
jlnav Aug 21, 2025
1dce059
small fixes
jlnav Aug 22, 2025
77845a0
docstring for APOSMM class
jlnav Aug 22, 2025
2635236
evaluate an APOSMM with only VOCS passed in
jlnav Aug 22, 2025
4e73c93
replace completely-typed out gen_specs['user'] update from parameters…
jlnav Aug 26, 2025
4357173
coverage adjusts
jlnav Aug 27, 2025
ddcddba
Bump crate-ci/typos from 1.35.7 to 1.36.2
dependabot[bot] Sep 8, 2025
4c10f40
Merge pull request #1589 from Libensemble/dependabot/github_actions/d…
jmlarson1 Sep 9, 2025
04485be
Adding test for ibcdfo with jax
jmlarson1 Sep 16, 2025
df4a395
clenaup
jmlarson1 Sep 16, 2025
b370e2b
Bump the python-updates group across 1 directory with 3 updates
dependabot[bot] Sep 16, 2025
e6769bc
Starting a manifold sampling example
jmlarson1 Sep 17, 2025
ad42858
Merge pull request #1592 from Libensemble/dependabot/pip/develop/pyth…
jmlarson1 Sep 17, 2025
0fcc9b5
Fixing ibcdfo tests
jmlarson1 Sep 17, 2025
3fe6376
black
jmlarson1 Sep 17, 2025
00b6fc0
Black
jmlarson1 Sep 17, 2025
63c082a
Updated IBCDFO branch
jmlarson1 Sep 17, 2025
5d0ae3d
reverting
jmlarson1 Sep 17, 2025
334dc8d
Updated IBCDFO branch
jmlarson1 Sep 17, 2025
756a854
Removing unused def
jmlarson1 Sep 18, 2025
92e65d3
Merge pull request #1591 from Libensemble/updating_ibcdfo_example
jmlarson1 Sep 18, 2025
38f3fcb
Merge pull request #1593 from Libensemble/formatting/black_2025_09_17
jmlarson1 Sep 22, 2025
385be08
Bump crate-ci/typos from 1.36.2 to 1.36.3
dependabot[bot] Sep 29, 2025
35610f5
Bump the python-updates group across 1 directory with 2 updates
dependabot[bot] Sep 29, 2025
6c0df1d
Updating IBCDFO branch
jmlarson1 Sep 30, 2025
0920ec1
Merge pull request #1599 from Libensemble/update_msp_branch
jmlarson1 Sep 30, 2025
f8e977d
Merge pull request #1597 from Libensemble/dependabot/github_actions/d…
jmlarson1 Sep 30, 2025
7858075
Merge branch 'develop' into dependabot/pip/develop/python-updates-ee9…
jmlarson1 Sep 30, 2025
ec7add0
Merge pull request #1598 from Libensemble/dependabot/pip/develop/pyth…
jmlarson1 Oct 1, 2025
f6665b0
Bump crate-ci/typos from 1.36.3 to 1.37.3
dependabot[bot] Oct 6, 2025
b7b539f
typo
jmlarson1 Oct 6, 2025
95dd275
typo
jmlarson1 Oct 6, 2025
1a68569
typo
jmlarson1 Oct 6, 2025
a565a4c
Merge pull request #1601 from Libensemble/dependabot/github_actions/d…
jmlarson1 Oct 6, 2025
11970ae
Bump crate-ci/typos from 1.37.3 to 1.38.1
dependabot[bot] Oct 13, 2025
d74e5f1
Bump the python-updates group with 3 updates
dependabot[bot] Oct 13, 2025
c8bee35
Merge pull request #1602 from Libensemble/dependabot/github_actions/d…
jmlarson1 Oct 13, 2025
beb9b88
Merge pull request #1603 from Libensemble/dependabot/pip/develop/pyth…
jmlarson1 Oct 14, 2025
1353363
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/apo…
jlnav Oct 23, 2025
bb26e05
Merge branch 'develop' into asktell/aposmm_fixes
jlnav Oct 23, 2025
948c88b
this specific gen_specs['out'] assignment no longer needed, as confli…
jlnav Oct 23, 2025
047673d
adjust for finalize and export; plus now variables_mapping is required
jlnav Oct 23, 2025
b681398
don't need persis_info declared like this
jlnav Oct 23, 2025
b828cb4
fixes and refactors as suggested by shuds - *works on multistage lpa*
jlnav Oct 24, 2025
c040721
make initial_sample_size and max_active_runs required arguments for a…
jlnav Oct 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/basic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: crate-ci/typos@v1.35.7
- uses: crate-ci/typos@v1.38.1
2 changes: 1 addition & 1 deletion .github/workflows/extra.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,4 +145,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: crate-ci/typos@v1.35.7
- uses: crate-ci/typos@v1.38.1
2 changes: 1 addition & 1 deletion install/install_ibcdfo.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

git clone --recurse-submodules -b develop https://github.com/POptUS/IBCDFO.git
git clone --recurse-submodules -b main https://github.com/POptUS/IBCDFO.git
pushd IBCDFO/minq/py/minq5/
export PYTHONPATH="$PYTHONPATH:$(pwd)"
echo "PYTHONPATH=$PYTHONPATH" >> $GITHUB_ENV
Expand Down
2 changes: 1 addition & 1 deletion install/misc_feature_requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
globus-compute-sdk==3.12.0
globus-compute-sdk==3.16.0
10 changes: 5 additions & 5 deletions install/testing_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
flake8==7.3.0
coverage>=7.5
pytest==8.4.1
pytest-cov==6.2.1
pytest==8.4.2
pytest-cov==7.0.0
pytest-timeout==2.4.0
mock==5.2.0
python-dateutil==2.9.0.post0
anyio==4.10.0
matplotlib==3.10.6
anyio==4.11.0
matplotlib==3.10.7
mpmath==1.3.0
rich==14.1.0
rich==14.2.0
131 changes: 104 additions & 27 deletions libensemble/gen_classes/aposmm.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import copy
from math import gamma, pi, sqrt
from typing import List

import numpy as np
Expand All @@ -11,9 +12,17 @@

class APOSMM(PersistentGenInterfacer):
"""
Standalone object-oriented APOSMM generator
APOSMM coordinates multiple local optimization runs, dramatically reducing time for
discovering multiple minima on parallel systems.

This *generator* adheres to the `Generator Standard <https://github.com/campa-consortium/generator_standard>`_.

.. seealso::

`https://doi.org/10.1007/s12532-017-0131-4 <https://doi.org/10.1007/s12532-017-0131-4>`_

VOCS variables must include both regular and *_on_cube versions. E.g.,:

vars_std = {
"var1": [-10.0, 10.0],
"var2": [0.0, 100.0],
Expand All @@ -26,53 +35,121 @@ class APOSMM(PersistentGenInterfacer):
"x": ["var1", "var2", "var3"],
"x_on_cube": ["var1_on_cube", "var2_on_cube", "var3_on_cube"],
}
gen = APOSMM(vocs, variables_mapping=variables_mapping, ...)
gen = APOSMM(vocs, 3, 3, variables_mapping=variables_mapping, ...)

Parameters
----------
vocs: VOCS
The VOCS object, adhering to the VOCS interface from the Generator Standard.

max_active_runs: int
Bound on number of runs APOSMM is advancing.

initial_sample_size: int
Number of uniformly sampled points to be evaluated internally before starting
the localopt runs. `.suggest()` will return samples from these points.

History: npt.NDArray = []
An optional history of previously evaluated points.

sample_points: npt.NDArray = None
Points to be sampled (original domain).
If more sample points are needed by APOSMM during the course of the
optimization, points will be drawn uniformly over the domain.

localopt_method: str = "LN_BOBYQA"
The local optimization method to use.

rk_const: float = None
Multiplier in front of the ``r_k`` value.
If not provided, it will be set to ``0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi)``

xtol_abs: float = 1e-6
Localopt method's convergence tolerance.

ftol_abs: float = 1e-6
Localopt method's convergence tolerance.

dist_to_bound_multiple: float = 0.5
What fraction of the distance to the nearest boundary should the initial
step size be in localopt runs.

random_seed: int = 1
Seed for the random number generator.
"""

def __init__(
self,
vocs: VOCS,
max_active_runs: int,
initial_sample_size: int,
History: npt.NDArray = [],
persis_info: dict = {},
gen_specs: dict = {},
libE_info: dict = {},
sample_points: npt.NDArray = None,
localopt_method: str = "LN_BOBYQA",
rk_const: float = None,
xtol_abs: float = 1e-6,
ftol_abs: float = 1e-6,
dist_to_bound_multiple: float = 0.5,
random_seed: int = 1,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should default be 0 or 1.

**kwargs,
) -> None:

from libensemble.gen_funcs.persistent_aposmm import aposmm

self.VOCS = vocs
gen_specs["gen_f"] = aposmm

gen_specs = {}
gen_specs["user"] = {}
persis_info = {}
libE_info = {}
gen_specs["gen_f"] = aposmm
n = len(list(vocs.variables.keys()))

if not rk_const:
rk_const = 0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi)

FIELDS = [
"initial_sample_size",
"sample_points",
"localopt_method",
"rk_const",
"xtol_abs",
"ftol_abs",
"dist_to_bound_multiple",
"max_active_runs",
]

for k in FIELDS:
val = locals().get(k)
if val is not None:
gen_specs["user"][k] = val

super().__init__(vocs, History, persis_info, gen_specs, libE_info, **kwargs)

# Set bounds using the correct x mapping
x_mapping = self.variables_mapping["x"]
self.gen_specs["user"]["lb"] = np.array([vocs.variables[var].domain[0] for var in x_mapping])
self.gen_specs["user"]["ub"] = np.array([vocs.variables[var].domain[1] for var in x_mapping])

if not gen_specs.get("out"):
x_size = len(self.variables_mapping.get("x", []))
x_on_cube_size = len(self.variables_mapping.get("x_on_cube", []))
assert x_size > 0 and x_on_cube_size > 0, "Both x and x_on_cube must be specified in variables_mapping"
assert (
x_size == x_on_cube_size
), f"x and x_on_cube must have same length but got {x_size} and {x_on_cube_size}"

gen_specs["out"] = [
("x", float, x_size),
("x_on_cube", float, x_on_cube_size),
("sim_id", int),
("local_min", bool),
("local_pt", bool),
]

gen_specs["persis_in"] = ["sim_id", "x", "x_on_cube", "f", "sim_ended"]
if "components" in kwargs or "components" in gen_specs.get("user", {}):
gen_specs["persis_in"].append("fvec")
x_size = len(self.variables_mapping.get("x", []))
x_on_cube_size = len(self.variables_mapping.get("x_on_cube", []))
assert x_size > 0 and x_on_cube_size > 0, "Both x and x_on_cube must be specified in variables_mapping"
assert x_size == x_on_cube_size, f"x and x_on_cube must have same length but got {x_size} and {x_on_cube_size}"

gen_specs["out"] = [
("x", float, x_size),
("x_on_cube", float, x_on_cube_size),
("sim_id", int),
("local_min", bool),
("local_pt", bool),
]

gen_specs["persis_in"] = ["sim_id", "x", "x_on_cube", "f", "sim_ended"]
if "components" in kwargs or "components" in gen_specs.get("user", {}):
gen_specs["persis_in"].append("fvec")

# SH - Need to know if this is gen_on_manager or not.
if not self.persis_info.get("nworkers"):
self.persis_info["nworkers"] = kwargs.get("nworkers", gen_specs["user"].get("max_active_runs", 4))
self.persis_info["nworkers"] = gen_specs["user"].get("max_active_runs")
self.all_local_minima = []
self._suggest_idx = 0
self._last_suggest = None
Expand Down
68 changes: 64 additions & 4 deletions libensemble/gen_funcs/aposmm_localopt_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"run_local_tao",
"run_local_dfols",
"run_local_ibcdfo_pounders",
"run_local_ibcdfo_manifold_sampling",
"run_local_scipy_opt",
"run_external_localopt",
]
Expand All @@ -27,7 +28,7 @@ class APOSMMException(Exception):
"""Raised for any exception in APOSMM"""


optimizer_list = ["petsc", "nlopt", "dfols", "scipy", "ibcdfo", "external"]
optimizer_list = ["petsc", "nlopt", "dfols", "scipy", "ibcdfo_pounders", "ibcdfo_manifold_sampling", "external"]
optimizers = libensemble.gen_funcs.rc.aposmm_optimizers

if optimizers is not None:
Expand All @@ -43,8 +44,10 @@ class APOSMMException(Exception):
import nlopt # noqa: F401
if "dfols" in optimizers:
import dfols # noqa: F401
if "ibcdfo" in optimizers:
from ibcdfo import pounders # noqa: F401
if "ibcdfo_pounders" in optimizers:
from ibcdfo.pounders import pounders # noqa: F401
if "ibcdfo_manifold_sampling" in optimizers:
from ibcdfo.manifold_sampling import manifold_sampling_primal # noqa: F401
if "scipy" in optimizers:
from scipy import optimize as sp_opt # noqa: F401
if "external_localopt" in optimizers:
Expand Down Expand Up @@ -80,6 +83,7 @@ class LocalOptInterfacer(object):
- PETSc/TAO [``'pounders'``, ``'blmvm'``, ``'nm'``]
- SciPy [``'scipy_Nelder-Mead'``, ``'scipy_COBYLA'``, ``'scipy_BFGS'``]
- DFOLS [``'dfols'``]
- IBCDFO [``'pounders'``, ``'manifold_sampling_primal'``]
- External local optimizer [``'external_localopt'``] (which use files to pass/receive ``x/f`` values)
"""

Expand Down Expand Up @@ -124,6 +128,8 @@ def __init__(self, user_specs, x0, f0, grad0=None):
run_local_opt = run_local_dfols
elif user_specs["localopt_method"] in ["ibcdfo_pounders"]:
run_local_opt = run_local_ibcdfo_pounders
elif user_specs["localopt_method"] in ["ibcdfo_manifold_sampling"]:
run_local_opt = run_local_ibcdfo_manifold_sampling
elif user_specs["localopt_method"] in ["external_localopt"]:
run_local_opt = run_external_localopt
else:
Expand Down Expand Up @@ -418,6 +424,60 @@ def run_local_dfols(user_specs, comm_queue, x0, f0, child_can_read, parent_can_r
finish_queue(x_opt, opt_flag, comm_queue, parent_can_read, user_specs)


def run_local_ibcdfo_manifold_sampling(user_specs, comm_queue, x0, f0, child_can_read, parent_can_read):
"""
Runs a IBCDFO local optimization run starting at ``x0``, governed by the
parameters in ``user_specs``.

Although IBCDFO methods can receive previous evaluations, few other methods
support that, so APOSMM assumes the first point will be re-evaluated (but
not be sent back to the manager).
"""
n = len(x0)
# Define bound constraints (lower <= x <= upper)
lb = np.zeros(n)
ub = np.ones(n)

# Set random seed (for reproducibility)
np.random.seed(0)

# dist_to_bound = min(min(ub - x0), min(x0 - lb))
# assert dist_to_bound > np.finfo(np.float64).eps, "The distance to the boundary is too small"

run_max_eval = user_specs.get("run_max_eval", 100 * (n + 1))
# g_tol = 1e-8
# delta_0 = 0.5 * dist_to_bound
# m = len(f0)
subprob_switch = "linprog"

[X, F, hF, xkin, flag] = manifold_sampling_primal(
user_specs["hfun"],
lambda x: scipy_dfols_callback_fun(x, comm_queue, child_can_read, parent_can_read, user_specs),
x0,
lb,
ub,
run_max_eval,
subprob_switch,
)

assert flag >= 0 or flag == -6, "IBCDFO errored"

x_opt = X[xkin]

if flag > 0:
opt_flag = 1
else:
print(
"[APOSMM] The IBCDFO run started from " + str(x0) + " stopped with an exit "
"flag of " + str(flag) + ". No point from this run will be "
"ruled as a minimum! APOSMM may start a new run from some point "
"in this run."
)
opt_flag = 0

finish_queue(x_opt, opt_flag, comm_queue, parent_can_read, user_specs)


def run_local_ibcdfo_pounders(user_specs, comm_queue, x0, f0, child_can_read, parent_can_read):
"""
Runs a IBCDFO local optimization run starting at ``x0``, governed by the
Expand Down Expand Up @@ -448,7 +508,7 @@ def run_local_ibcdfo_pounders(user_specs, comm_queue, x0, f0, child_can_read, pa
else:
Options = None

[X, F, hF, flag, xkin] = pounders.pounders(
[X, F, hF, flag, xkin] = pounders(
lambda x: scipy_dfols_callback_fun(x, comm_queue, child_can_read, parent_can_read, user_specs),
x0,
n,
Expand Down
2 changes: 1 addition & 1 deletion libensemble/gen_funcs/persistent_aposmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ def initialize_children(user_specs):
"nm",
]:
fields_to_pass = ["x_on_cube", "f"]
elif user_specs["localopt_method"] in ["pounders", "ibcdfo_pounders", "dfols"]:
elif user_specs["localopt_method"] in ["pounders", "ibcdfo_pounders", "ibcdfo_manifold_sampling", "dfols"]:
fields_to_pass = ["x_on_cube", "fvec"]
else:
raise NotImplementedError(f"Unknown local optimization method {user_specs['localopt_method']}.")
Expand Down
4 changes: 2 additions & 2 deletions libensemble/gen_funcs/persistent_ax_multitask.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,9 @@ def max_utility_from_GP(n, m, gr, hifi_task):
f, cov = m.predict(obsf)
# Compute expected utility
u = -np.array(f["hifi_metric"])
best_arm_indx = np.flip(np.argsort(u))[:n]
best_arm_index = np.flip(np.argsort(u))[:n]
gr_new = GeneratorRun(
arms=[gr.arms[i] for i in best_arm_indx],
arms=[gr.arms[i] for i in best_arm_index],
weights=[1.0] * n,
)
return gr_new
2 changes: 1 addition & 1 deletion libensemble/gen_funcs/persistent_gpCAM.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def persistent_gpCAM(H_in, persis_info, gen_specs, libE_info):
"""
This generation function constructs a global surrogate of `f` values. It is
a batched method that produces a first batch uniformly random from (lb, ub).
On subequent iterations, it calls an optimization method to produce the next
On subsequent iterations, it calls an optimization method to produce the next
batch of points. This optimization might be too slow (relative to the
simulation evaluation time) for some use cases.

Expand Down
2 changes: 1 addition & 1 deletion libensemble/gen_funcs/persistent_sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def _get_user_params(user_specs):


@persistent_input_fields(["sim_id"])
@output_data([("x", float, (2,))]) # The dimesion of 2 is a default and can be overwritten
@output_data([("x", float, (2,))]) # The dimension of 2 is a default and can be overwritten
def persistent_uniform(_, persis_info, gen_specs, libE_info):
"""
This generation function always enters into persistent mode and returns
Expand Down
Loading
Loading