Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2b8e537
initial commit - adding variables/objectives to initializer signature…
jlnav Oct 14, 2024
64c2cd1
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Oct 18, 2024
3104240
experiment with UniformSampleDicts using variables/objectiveso
jlnav Oct 18, 2024
3d262fb
i wonder if we can determine lb, ub, and n based on the contents of s…
jlnav Oct 21, 2024
847a617
APOSMM can now accept variables and objectives instead of needing ub,…
jlnav Oct 22, 2024
f45ddbe
cleanup the removed validator; since gen_specs['out'] can be absent
jlnav Oct 22, 2024
26f1d73
cleanup/fixes
jlnav Oct 22, 2024
10e96d8
stop kwargs from replacing entire gen_specs.user; try out vars/objs w…
jlnav Oct 22, 2024
23b1549
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Oct 23, 2024
5f777c2
additional experiments with vars/objs, including seeing if we can app…
jlnav Oct 25, 2024
ac5467b
moving logic for determining lb and ub from variables into parent cla…
jlnav Oct 28, 2024
85507a4
init pair of functions for mapping, slot in where they'll be called
jlnav Oct 28, 2024
fc30284
remove a debugging print
jlnav Oct 28, 2024
f9e3cba
small fixes, and first tentative implementation of converter for xs t…
jlnav Nov 1, 2024
14c36fa
perhaps the input conversion will be easier on a numpy array?
jlnav Nov 1, 2024
25299e7
tentatively complete converter for vars/objs -> x/f. but those xs and…
jlnav Nov 1, 2024
f0736fb
some cleanup
jlnav Nov 1, 2024
7fa4d1e
fix continue-condition to occur earlier if we're looking at keys we d…
jlnav Nov 4, 2024
14daf3c
test fixes, plus if our gen naturally returns the requested variables…
jlnav Nov 4, 2024
114c7a4
fix asktell_gen functionality test - including removing wrapper tests…
jlnav Nov 4, 2024
507bc0a
just use UniformSample class
jlnav Nov 4, 2024
18a52c9
remove ask/tell surmise and ask/tell surmise test - they were proof-o…
jlnav Nov 4, 2024
231e6f0
fix import
jlnav Nov 4, 2024
eaebbff
remove the other ask/tell surmise test
jlnav Nov 4, 2024
043feeb
renable persistent_aposmm unit test
jlnav Nov 5, 2024
c380595
preparing to add variables_mapping to LibensembleGenerator parent cla…
jlnav Nov 6, 2024
1e0abd3
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Nov 7, 2024
c7ea54b
intermediate work on passing mapping into np_to_list_dicts. need to p…
jlnav Nov 7, 2024
0ee448c
use mapping to construct list_dicts_to_np dtype when provided
jlnav Nov 8, 2024
bb37f4b
additional work on replacing dict keys with xs and fs
jlnav Nov 8, 2024
38b3967
some cleanup of generators.py in anticipation of the changes to the d…
jlnav Nov 8, 2024
4b49233
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Nov 11, 2024
1d213ef
dont try to determine dtype for fields that aren't actually in the in…
jlnav Nov 11, 2024
f8c5eaf
finalize mapping support within list_dicts_to_np, now need to refacto…
jlnav Nov 11, 2024
dff6bad
refactoring
jlnav Nov 11, 2024
c1ec7f6
tiny fixes; need to figure out why aposmm_nlopt reg test is hanging
jlnav Nov 11, 2024
a5133b9
runners.py no longer calls setup() on gen
jlnav Nov 11, 2024
9f200f0
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Nov 12, 2024
f2ef248
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Nov 13, 2024
2c6a9c4
lock nlopt to 2.8.0?
jlnav Nov 13, 2024
99a7a2c
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Nov 14, 2024
bc1587e
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Nov 14, 2024
5c2308d
avoid redundant install of nlopt?
jlnav Nov 14, 2024
ef906d5
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Nov 20, 2024
902b7f0
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Nov 20, 2024
d66dafb
Merge branch 'experimental/jlnav_plus_shuds_asktell' into asktell/var…
jlnav Dec 4, 2024
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ dist/
.spyproject/

.hypothesis
.pixi
1 change: 0 additions & 1 deletion libensemble/gen_classes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from .aposmm import APOSMM # noqa: F401
from .sampling import UniformSample, UniformSampleDicts # noqa: F401
from .surmise import Surmise # noqa: F401
27 changes: 19 additions & 8 deletions libensemble/gen_classes/aposmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

from libensemble.generators import LibensembleGenThreadInterfacer
from libensemble.message_numbers import EVAL_GEN_TAG, PERSIS_STOP
from libensemble.tools import add_unique_random_streams


class APOSMM(LibensembleGenThreadInterfacer):
Expand All @@ -15,24 +14,36 @@ class APOSMM(LibensembleGenThreadInterfacer):
"""

def __init__(
self, History: npt.NDArray = [], persis_info: dict = {}, gen_specs: dict = {}, libE_info: dict = {}, **kwargs
self,
variables: dict,
objectives: dict,
History: npt.NDArray = [],
persis_info: dict = {},
gen_specs: dict = {},
libE_info: dict = {},
**kwargs
) -> None:
from libensemble.gen_funcs.persistent_aposmm import aposmm

self.variables = variables
self.objectives = objectives

gen_specs["gen_f"] = aposmm

if not gen_specs.get("out"): # gen_specs never especially changes for aposmm even as the problem varies
n = len(kwargs["lb"]) or len(kwargs["ub"])
if not self.variables:
self.n = len(kwargs["lb"]) or len(kwargs["ub"])
else:
self.n = len(self.variables)
gen_specs["out"] = [
("x", float, n),
("x_on_cube", float, n),
("x", float, self.n),
("x_on_cube", float, self.n),
("sim_id", int),
("local_min", bool),
("local_pt", bool),
]
gen_specs["persis_in"] = ["x", "f", "local_pt", "sim_id", "sim_ended", "x_on_cube", "local_min"]
if not persis_info:
persis_info = add_unique_random_streams({}, 2, seed=4321)[1]
super().__init__(History, persis_info, gen_specs, libE_info, **kwargs)
super().__init__(variables, objectives, History, persis_info, gen_specs, libE_info, **kwargs)
if not self.persis_info.get("nworkers"):
self.persis_info["nworkers"] = kwargs.get("nworkers", gen_specs["user"]["max_active_runs"])
self.all_local_minima = []
Expand Down
35 changes: 15 additions & 20 deletions libensemble/gen_classes/sampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy as np

from libensemble.generators import Generator, LibensembleGenerator
from libensemble.utils.misc import list_dicts_to_np

__all__ = [
"UniformSample",
Expand Down Expand Up @@ -31,14 +32,16 @@ class UniformSample(SampleBase):
mode by adjusting the allocation function.
"""

def __init__(self, _=[], persis_info={}, gen_specs={}, libE_info=None, **kwargs):
super().__init__(_, persis_info, gen_specs, libE_info, **kwargs)
def __init__(self, variables: dict, objectives: dict, _=[], persis_info={}, gen_specs={}, libE_info=None, **kwargs):
super().__init__(variables, objectives, _, persis_info, gen_specs, libE_info, **kwargs)
self._get_user_params(self.gen_specs["user"])

def ask_numpy(self, n_trials):
H_o = np.zeros(n_trials, dtype=self.gen_specs["out"])
H_o["x"] = self.persis_info["rand_stream"].uniform(self.lb, self.ub, (n_trials, self.n))
return H_o
return list_dicts_to_np(
UniformSampleDicts(
self.variables, self.objectives, self.History, self.persis_info, self.gen_specs, self.libE_info
).ask(n_trials)
)

def tell_numpy(self, calc_in):
pass # random sample so nothing to tell
Expand All @@ -53,31 +56,23 @@ class UniformSampleDicts(Generator):
sampled points the first time it is called. Afterwards, it returns the
number of points given. This can be used in either a batch or asynchronous
mode by adjusting the allocation function.

This currently adheres to the complete standard.
"""

def __init__(self, _, persis_info, gen_specs, libE_info=None, **kwargs):
def __init__(self, variables: dict, objectives: dict, _, persis_info, gen_specs, libE_info=None, **kwargs):
self.variables = variables
self.gen_specs = gen_specs
self.persis_info = persis_info
self._get_user_params(self.gen_specs["user"])

def ask(self, n_trials):
H_o = []
for _ in range(n_trials):
# using same rand number stream
trial = {"x": self.persis_info["rand_stream"].uniform(self.lb, self.ub, self.n)}
trial = {}
for key in self.variables.keys():
trial[key] = self.persis_info["rand_stream"].uniform(self.variables[key][0], self.variables[key][1])
H_o.append(trial)
return H_o

def tell(self, calc_in):
pass # random sample so nothing to tell

# Duplicated for now
def _get_user_params(self, user_specs):
"""Extract user params"""
# b = user_specs["initial_batch_size"]
self.ub = user_specs["ub"]
self.lb = user_specs["lb"]
self.n = len(self.lb) # dimension
assert isinstance(self.n, int), "Dimension must be an integer"
assert isinstance(self.lb, np.ndarray), "lb must be a numpy array"
assert isinstance(self.ub, np.ndarray), "ub must be a numpy array"
60 changes: 0 additions & 60 deletions libensemble/gen_classes/surmise.py

This file was deleted.

92 changes: 63 additions & 29 deletions libensemble/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"""


class GeneratorNotStartedException(Exception):
"""Exception raised by a threaded/multiprocessed generator upon being asked without having been started"""


class Generator(ABC):
"""

Expand All @@ -32,9 +36,9 @@ class Generator(ABC):


class MyGenerator(Generator):
def __init__(self, param):
def __init__(self, variables, objectives, param):
self.param = param
self.model = None
self.model = create_model(variables, objectives, self.param)

def ask(self, num_points):
return create_points(num_points, self.param)
Expand All @@ -47,12 +51,15 @@ def final_tell(self, results):
return list(self.model)


my_generator = MyGenerator(my_parameter=100)
variables = {"a": [-1, 1], "b": [-2, 2]}
objectives = {"f": "MINIMIZE"}

my_generator = MyGenerator(variables, objectives, my_parameter=100)
gen_specs = GenSpecs(generator=my_generator, ...)
"""

@abstractmethod
def __init__(self, *args, **kwargs):
def __init__(self, variables: dict[str, List[float]], objectives: dict[str, str], *args, **kwargs):
"""
Initialize the Generator object on the user-side. Constants, class-attributes,
and preparation goes here.
Expand Down Expand Up @@ -94,12 +101,45 @@ class LibensembleGenerator(Generator):
"""

def __init__(
self, History: npt.NDArray = [], persis_info: dict = {}, gen_specs: dict = {}, libE_info: dict = {}, **kwargs
self,
variables: dict,
objectives: dict = {},
History: npt.NDArray = [],
persis_info: dict = {},
gen_specs: dict = {},
libE_info: dict = {},
**kwargs,
):
self.variables = variables
self.objectives = objectives
self.History = History
self.gen_specs = gen_specs
self.libE_info = libE_info

self.variables_mapping = kwargs.get("variables_mapping", {})

self._internal_variable = "x" # need to figure these out dynamically
self._internal_objective = "f"

if self.variables:

self.n = len(self.variables)
# build our own lb and ub
if "lb" not in kwargs and "ub" not in kwargs:
Copy link
Member

Choose a reason for hiding this comment

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

Have you tested if these are in kargs?

Copy link
Member

Choose a reason for hiding this comment

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

If I forget to comment out my lb/ub line I get:
...persistent_aposmm.py", line 326, in update_local_H_after_receiving
local_H[name][Work["libE_info"]["H_rows"]] = calc_in[name]
ValueError: shape mismatch: value array of shape (4,6) could not be broadcast to indexing result of shape (4,3)

Copy link
Member

Choose a reason for hiding this comment

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

That was with our branches merged.

lb = []
ub = []
for i, v in enumerate(self.variables.values()):
if isinstance(v, list) and (isinstance(v[0], int) or isinstance(v[0], float)):
lb.append(v[0])
ub.append(v[1])
kwargs["lb"] = np.array(lb)
kwargs["ub"] = np.array(ub)

if len(kwargs) > 0: # so user can specify gen-specific parameters as kwargs to constructor
self.gen_specs["user"] = kwargs
if not persis_info:
if not self.gen_specs.get("user"):
self.gen_specs["user"] = {}
self.gen_specs["user"].update(kwargs)
if not persis_info.get("rand_stream"):
self.persis_info = add_unique_random_streams({}, 4, seed=4321)[1]
else:
self.persis_info = persis_info
Expand All @@ -121,13 +161,13 @@ def convert_np_types(dict_list):

def ask(self, num_points: Optional[int] = 0) -> List[dict]:
"""Request the next set of points to evaluate."""
return LibensembleGenerator.convert_np_types(np_to_list_dicts(self.ask_numpy(num_points)))
return LibensembleGenerator.convert_np_types(
np_to_list_dicts(self.ask_numpy(num_points), mapping=self.variables_mapping)
)

def tell(self, results: List[dict]) -> None:
"""Send the results of evaluations to the generator."""
self.tell_numpy(list_dicts_to_np(results))
# Note that although we'd prefer to have a complete dtype available, the gen
# doesn't have access to sim_specs["out"] currently.
self.tell_numpy(list_dicts_to_np(results, mapping=self.variables_mapping))


class LibensembleGenThreadInterfacer(LibensembleGenerator):
Expand All @@ -136,36 +176,30 @@ class LibensembleGenThreadInterfacer(LibensembleGenerator):
"""

def __init__(
self, History: npt.NDArray = [], persis_info: dict = {}, gen_specs: dict = {}, libE_info: dict = {}, **kwargs
self,
variables: dict,
objectives: dict = {},
History: npt.NDArray = [],
persis_info: dict = {},
gen_specs: dict = {},
libE_info: dict = {},
**kwargs,
) -> None:
super().__init__(History, persis_info, gen_specs, libE_info, **kwargs)
super().__init__(variables, objectives, History, persis_info, gen_specs, libE_info, **kwargs)
self.gen_f = gen_specs["gen_f"]
self.History = History
self.persis_info = persis_info
self.libE_info = libE_info
self.thread = None

def setup(self) -> None:
"""Must be called once before calling ask/tell. Initializes the background thread."""
# self.inbox = thread_queue.Queue() # sending betweween HERE and gen
# self.outbox = thread_queue.Queue()

if self.thread is not None:
return
# SH this contains the thread lock - removing.... wrong comm to pass on anyway.
if hasattr(Executor.executor, "comm"):
del Executor.executor.comm
self.libE_info["executor"] = Executor.executor

# SH - fix comment (thread and process & name object appropriately - task? qcomm?)
# self.thread = QCommThread( # TRY A PROCESS
# self.gen_f,
# None,
# self.History,
# self.persis_info,
# self.gen_specs,
# self.libE_info,
# user_function=True,
# ) # note that self.thread's inbox/outbox are unused by the underlying gen

self.thread = QCommProcess( # TRY A PROCESS
self.gen_f,
None,
Expand All @@ -191,7 +225,7 @@ def _set_sim_ended(self, results: npt.NDArray) -> npt.NDArray:

def tell(self, results: List[dict], tag: int = EVAL_GEN_TAG) -> None:
"""Send the results of evaluations to the generator."""
self.tell_numpy(list_dicts_to_np(results), tag)
self.tell_numpy(list_dicts_to_np(results, mapping=self.variables_mapping), tag)

def ask_numpy(self, num_points: int = 0) -> npt.NDArray:
"""Request the next set of points to evaluate, as a NumPy array."""
Expand Down
Loading
Loading