Skip to content

Commit e89e62d

Browse files
authored
Merge pull request #1635 from Libensemble/examples/optimas_ax_generators
Examples/optimas ax generators
2 parents 38aa584 + a9b76ef commit e89e62d

8 files changed

Lines changed: 335 additions & 16 deletions

File tree

libensemble/history.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ def __init__(
8282
H = np.zeros(L + len(H0), dtype=specs_dtype_list)
8383

8484
H["sim_id"][-L:] = -1
85+
if "_id" in H.dtype.names:
86+
H["_id"][-L:] = -1
8587
H["sim_started_time"][-L:] = np.inf
8688
H["gen_informed_time"][-L:] = np.inf
8789

@@ -270,6 +272,8 @@ def grow_H(self, k: int) -> None:
270272
"""
271273
H_1 = np.zeros(k, dtype=self.H.dtype)
272274
H_1["sim_id"] = -1
275+
if "_id" in H_1.dtype.names:
276+
H_1["_id"] = -1
273277
H_1["sim_started_time"] = np.inf
274278
H_1["gen_informed_time"] = np.inf
275279
if "resource_sets" in H_1.dtype.names:

libensemble/manager.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,14 @@ def _freeup_resources(self, w: int) -> None:
410410
if self.resources:
411411
self.resources.resource_manager.free_rsets(w)
412412

413+
def _ensure_sim_id_in_persis_in(self, D: npt.NDArray) -> None:
414+
"""Add sim_id to gen_specs persis_in if generator output contains sim_id (gest-api style generators only)"""
415+
if self.gen_specs.get("generator") and len(D) > 0 and "sim_id" in D.dtype.names:
416+
if "persis_in" not in self.gen_specs:
417+
self.gen_specs["persis_in"] = []
418+
if "sim_id" not in self.gen_specs["persis_in"]:
419+
self.gen_specs["persis_in"].append("sim_id")
420+
413421
def _send_work_order(self, Work: dict, w: int) -> None:
414422
"""Sends an allocation function order to a worker"""
415423
logger.debug(f"Manager sending work unit to worker {w}")
@@ -483,6 +491,7 @@ def _update_state_on_worker_msg(self, persis_info: dict, D_recv: dict, w: int) -
483491
final_data = D_recv.get("calc_out", None)
484492
if isinstance(final_data, np.ndarray):
485493
if calc_status is FINISHED_PERSISTENT_GEN_TAG and self.libE_specs.get("use_persis_return_gen", False):
494+
self._ensure_sim_id_in_persis_in(final_data)
486495
self.hist.update_history_x_in(w, final_data, self.W[w]["gen_started_time"])
487496
elif calc_status is FINISHED_PERSISTENT_SIM_TAG and self.libE_specs.get("use_persis_return_sim", False):
488497
self.hist.update_history_f(D_recv, self.kill_canceled_sims)
@@ -500,7 +509,9 @@ def _update_state_on_worker_msg(self, persis_info: dict, D_recv: dict, w: int) -
500509
if calc_type == EVAL_SIM_TAG:
501510
self.hist.update_history_f(D_recv, self.kill_canceled_sims)
502511
if calc_type == EVAL_GEN_TAG:
503-
self.hist.update_history_x_in(w, D_recv["calc_out"], self.W[w]["gen_started_time"])
512+
D = D_recv["calc_out"]
513+
self._ensure_sim_id_in_persis_in(D)
514+
self.hist.update_history_x_in(w, D, self.W[w]["gen_started_time"])
504515
assert (
505516
len(D_recv["calc_out"]) or np.any(self.W["active"]) or self.W[w]["persis_state"]
506517
), "Gen must return work when is is the only thing active and not persistent."

libensemble/specs.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,10 @@ def set_fields_from_vocs(self):
247247
persis_in_fields.extend(list(obj.keys()))
248248
self.persis_in = persis_in_fields
249249

250+
# Set inputs: same as persis_in for gest-api generators (needed for H0 ingestion)
251+
if not self.inputs and self.generator is not None:
252+
self.inputs = self.persis_in
253+
250254
# Set outputs: variables + constants (what the generator produces)
251255
if not self.outputs:
252256
out_fields = []
@@ -257,6 +261,17 @@ def set_fields_from_vocs(self):
257261
out_fields.append(_convert_dtype_to_output_tuple(name, dtype))
258262
self.outputs = out_fields
259263

264+
# Add _id field if generator returns_id is True
265+
if self.generator is not None and getattr(self.generator, "returns_id", False):
266+
if self.outputs is None:
267+
self.outputs = []
268+
if "_id" not in [f[0] for f in self.outputs]:
269+
self.outputs.append(("_id", int))
270+
if self.persis_in is None:
271+
self.persis_in = []
272+
if "_id" not in self.persis_in:
273+
self.persis_in.append("_id")
274+
260275
return self
261276

262277

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""
2+
Tests libEnsemble with Optimas Multi-Fidelity Ax Generator
3+
4+
*****currently fixing nworkers to batch_size*****
5+
6+
Execute via one of the following commands (e.g. 4 workers):
7+
mpiexec -np 5 python test_optimas_ax_mf.py
8+
python test_optimas_ax_mf.py -n 4
9+
10+
When running with the above commands, the number of concurrent evaluations of
11+
the objective function will be 4 as the generator is on the manager.
12+
13+
"""
14+
15+
# Do not change these lines - they are parsed by run-tests.sh
16+
# TESTSUITE_COMMS: mpi local
17+
# TESTSUITE_NPROCS: 4
18+
# TESTSUITE_EXTRA: true
19+
20+
import numpy as np
21+
22+
from gest_api.vocs import VOCS
23+
from optimas.generators import AxMultiFidelityGenerator
24+
25+
from libensemble import Ensemble
26+
from libensemble.alloc_funcs.start_only_persistent import only_persistent_gens as alloc_f
27+
from libensemble.specs import AllocSpecs, ExitCriteria, GenSpecs, LibeSpecs, SimSpecs
28+
29+
30+
def eval_func_mf(input_params):
31+
"""Evaluation function for multifidelity test."""
32+
x0 = input_params["x0"]
33+
x1 = input_params["x1"]
34+
resolution = input_params["res"]
35+
result = -(
36+
(x0 + 10 * np.cos(x0 + 0.1 * resolution))
37+
* (x1 + 5 * np.cos(x1 - 0.2 * resolution))
38+
)
39+
return {"f": result}
40+
41+
42+
# Main block is necessary only when using local comms with spawn start method (default on macOS and Windows).
43+
if __name__ == "__main__":
44+
45+
n = 2
46+
batch_size = 2
47+
48+
libE_specs = LibeSpecs(gen_on_manager=True, nworkers=batch_size)
49+
50+
vocs = VOCS(
51+
variables={"x0": [-50.0, 5.0], "x1": [-5.0, 15.0], "res": [1.0, 8.0]},
52+
objectives={"f": "MAXIMIZE"},
53+
)
54+
55+
gen = AxMultiFidelityGenerator(vocs=vocs)
56+
57+
gen_specs = GenSpecs(
58+
generator=gen,
59+
batch_size=batch_size,
60+
vocs=vocs,
61+
)
62+
63+
sim_specs = SimSpecs(
64+
simulator=eval_func_mf,
65+
vocs=vocs,
66+
)
67+
68+
alloc_specs = AllocSpecs(alloc_f=alloc_f)
69+
exit_criteria = ExitCriteria(sim_max=6)
70+
71+
workflow = Ensemble(
72+
libE_specs=libE_specs,
73+
sim_specs=sim_specs,
74+
alloc_specs=alloc_specs,
75+
gen_specs=gen_specs,
76+
exit_criteria=exit_criteria,
77+
)
78+
79+
H, _, _ = workflow.run()
80+
81+
# Perform the run
82+
if workflow.is_manager:
83+
workflow.save_output(__file__)
84+
print(f"Completed {len(H)} simulations")
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Tests libEnsemble with Optimas Multitask Ax Generator
3+
4+
Runs an initial ensemble, followed by another using the first as an H0.
5+
6+
*****currently fixing nworkers to batch_size*****
7+
8+
Execute via one of the following commands (e.g. 4 workers):
9+
mpiexec -np 5 python test_optimas_ax_multitask.py
10+
python test_optimas_ax_multitask.py -n 4
11+
12+
When running with the above commands, the number of concurrent evaluations of
13+
the objective function will be 4 as the generator is on the manager.
14+
15+
Issues: In some cases, the generator fails to produce points. This is
16+
intermittent and can be seen by the message "alloc_f did not return any work".
17+
This needs to be resolved in the generator by generating extra points
18+
as needed (exluding from until then).
19+
"""
20+
21+
# Do not change these lines - they are parsed by run-tests.sh
22+
# TESTSUITE_COMMS: local
23+
# TESTSUITE_NPROCS: 4
24+
# TESTSUITE_EXTRA: true
25+
# TESTSUITE_EXCLUDE: true
26+
27+
import numpy as np
28+
from gest_api.vocs import VOCS
29+
30+
from optimas.core import Task
31+
from optimas.generators import AxMultitaskGenerator
32+
33+
from libensemble import Ensemble
34+
from libensemble.alloc_funcs.start_only_persistent import only_persistent_gens as alloc_f
35+
from libensemble.specs import AllocSpecs, ExitCriteria, GenSpecs, LibeSpecs, SimSpecs
36+
37+
38+
def eval_func_multitask(input_params):
39+
"""Evaluation function for task1 or task2 in multitask test"""
40+
print(f'input_params: {input_params}')
41+
x0 = input_params["x0"]
42+
x1 = input_params["x1"]
43+
trial_type = input_params["trial_type"]
44+
45+
if trial_type == "task_1":
46+
result = -(x0 + 10 * np.cos(x0)) * (x1 + 5 * np.cos(x1))
47+
else:
48+
result = -0.5 * (x0 + 10 * np.cos(x0)) * (x1 + 5 * np.cos(x1))
49+
50+
output_params = {"f": result}
51+
return output_params
52+
53+
54+
# Main block is necessary only when using local comms with spawn start method (default on macOS and Windows).
55+
if __name__ == "__main__":
56+
57+
n = 2
58+
batch_size = 2
59+
60+
libE_specs = LibeSpecs(gen_on_manager=True, nworkers=batch_size)
61+
62+
vocs = VOCS(
63+
variables={
64+
"x0": [-50.0, 5.0],
65+
"x1": [-5.0, 15.0],
66+
"trial_type": {"task_1", "task_2"},
67+
},
68+
objectives={"f": "MAXIMIZE"},
69+
)
70+
71+
sim_specs = SimSpecs(
72+
simulator=eval_func_multitask,
73+
vocs=vocs,
74+
)
75+
76+
alloc_specs = AllocSpecs(alloc_f=alloc_f)
77+
exit_criteria = ExitCriteria(sim_max=15)
78+
79+
H0 = None # or np.load("multitask_first_pass.npy")
80+
for run_num in range(2):
81+
print(f"\nRun number: {run_num}")
82+
task1 = Task("task_1", n_init=2, n_opt=1)
83+
task2 = Task("task_2", n_init=5, n_opt=3)
84+
gen = AxMultitaskGenerator(vocs=vocs, hifi_task=task1, lofi_task=task2)
85+
86+
gen_specs = GenSpecs(
87+
generator=gen,
88+
batch_size=batch_size,
89+
vocs=vocs,
90+
)
91+
92+
workflow = Ensemble(
93+
libE_specs=libE_specs,
94+
sim_specs=sim_specs,
95+
alloc_specs=alloc_specs,
96+
gen_specs=gen_specs,
97+
exit_criteria=exit_criteria,
98+
H0=H0,
99+
)
100+
101+
H, _, _ = workflow.run()
102+
103+
if run_num == 0:
104+
H0 = H
105+
workflow.save_output("multitask_first_pass", append_attrs=False) # Allows restart only run
106+
107+
if workflow.is_manager:
108+
if run_num == 1:
109+
workflow.save_output("multitask_with_H0")
110+
print(f"Second run completed: {len(H)} simulations")
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
"""
2+
Tests libEnsemble with Optimas Single-Fidelity Ax Generator
3+
4+
*****currently fixing nworkers to batch_size*****
5+
6+
Execute via one of the following commands (e.g. 4 workers):
7+
mpiexec -np 5 python test_optimas_ax_sf.py
8+
python test_optimas_ax_sf.py -n 4
9+
10+
When running with the above commands, the number of concurrent evaluations of
11+
the objective function will be 4 as the generator is on the manager.
12+
13+
"""
14+
15+
# Do not change these lines - they are parsed by run-tests.sh
16+
# TESTSUITE_COMMS: mpi local
17+
# TESTSUITE_NPROCS: 4
18+
# TESTSUITE_EXTRA: true
19+
20+
import numpy as np
21+
22+
from gest_api.vocs import VOCS
23+
from optimas.generators import AxSingleFidelityGenerator
24+
25+
from libensemble import Ensemble
26+
from libensemble.alloc_funcs.start_only_persistent import only_persistent_gens as alloc_f
27+
from libensemble.specs import AllocSpecs, ExitCriteria, GenSpecs, LibeSpecs, SimSpecs
28+
29+
30+
def eval_func_sf(input_params):
31+
"""Evaluation function for single-fidelity test. """
32+
x0 = input_params["x0"]
33+
x1 = input_params["x1"]
34+
result = -(x0 + 10 * np.cos(x0)) * (x1 + 5 * np.cos(x1))
35+
return {"f": result}
36+
37+
38+
# Main block is necessary only when using local comms with spawn start method (default on macOS and Windows).
39+
if __name__ == "__main__":
40+
41+
n = 2
42+
batch_size = 2
43+
44+
libE_specs = LibeSpecs(gen_on_manager=True, nworkers=batch_size)
45+
46+
vocs = VOCS(
47+
variables={
48+
"x0": [-50.0, 5.0],
49+
"x1": [-5.0, 15.0],
50+
},
51+
objectives={"f": "MAXIMIZE"},
52+
)
53+
54+
gen = AxSingleFidelityGenerator(vocs=vocs)
55+
56+
gen_specs = GenSpecs(
57+
generator=gen,
58+
batch_size=batch_size,
59+
vocs=vocs,
60+
)
61+
62+
sim_specs = SimSpecs(
63+
simulator=eval_func_sf,
64+
vocs=vocs,
65+
)
66+
67+
alloc_specs = AllocSpecs(alloc_f=alloc_f)
68+
exit_criteria = ExitCriteria(sim_max=10)
69+
70+
workflow = Ensemble(
71+
libE_specs=libE_specs,
72+
sim_specs=sim_specs,
73+
alloc_specs=alloc_specs,
74+
gen_specs=gen_specs,
75+
exit_criteria=exit_criteria,
76+
)
77+
78+
H, _, _ = workflow.run()
79+
80+
# Perform the run
81+
if workflow.is_manager:
82+
workflow.save_output(__file__)
83+
print(f"Completed {len(H)} simulations")

0 commit comments

Comments
 (0)