Skip to content
Open
Changes from 3 commits
Commits
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
81 changes: 77 additions & 4 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,29 +12,101 @@

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>`_
Parameters
----------
vocs: VOCS
The VOCS object, adhering to the VOCS interface from the Generator Standard.
History: npt.NDArray = []
An optional history of previously evaluated points.
initial_sample_size: int = 100
Number of uniformly sampled points
to be evaluated before starting the localopt runs. Can be
zero if no additional sampling is desired, but if zero there must be past values
provided in the History.
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.
max_active_runs: int = 6
Bound on number of runs APOSMM is advancing.
random_seed: int = 1
Seed for the random number generator.
"""

def __init__(
self,
vocs: VOCS,
History: npt.NDArray = [],
persis_info: dict = {},
gen_specs: dict = {},
libE_info: dict = {},
initial_sample_size: int = 100,
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,
max_active_runs: int = 6,
random_seed: int = 1,
Copy link
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 = {}
persis_info = {"1": np.random.default_rng(random_seed)}
Copy link
Member

@shuds13 shuds13 Oct 1, 2025

Choose a reason for hiding this comment

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

why is this with key "1", I dont think we want that.

libE_info = {}
gen_specs["gen_f"] = aposmm
self.n = len(list(self.VOCS.variables.keys()))

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

gen_specs["user"] = {}
gen_specs["user"]["lb"] = np.array([vocs.variables[i].domain[0] for i in vocs.variables])
gen_specs["user"]["ub"] = np.array([vocs.variables[i].domain[1] for i in vocs.variables])

gen_specs["user"]["initial_sample_size"] = initial_sample_size
if sample_points is not None:
gen_specs["user"]["sample_points"] = sample_points
gen_specs["user"]["localopt_method"] = localopt_method
gen_specs["user"]["rk_const"] = rk_const
gen_specs["user"]["xtol_abs"] = xtol_abs
gen_specs["user"]["ftol_abs"] = ftol_abs
gen_specs["user"]["dist_to_bound_multiple"] = dist_to_bound_multiple
gen_specs["user"]["max_active_runs"] = max_active_runs
Copy link
Member

Choose a reason for hiding this comment

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

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

u = gen_specs.setdefault("user", {})
temp = {}
for k in FIELDS:
    val = locals().get(k)
    if val is not None:
        temp[k] = val
u.update(temp)

or separate required v optional

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe this works but grabbing values from locals() to prevent redundancy just looks odd to me. I can still implement this.

Copy link
Member

Choose a reason for hiding this comment

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

Its just a way to convert a string to a function name. Better than typing them all out right?


if not gen_specs.get("out"): # gen_specs never especially changes for aposmm even as the problem varies
gen_specs["out"] = [
("x", float, self.n),
Expand Down
Loading