Skip to content

Commit 8101c3b

Browse files
committed
migrate many alloc_f["user"] options to gen_specs. remove corresponding alloc_f import from many tests. mypy and formatting adjustments. first approach at describing options in specs.py
1 parent dece715 commit 8101c3b

38 files changed

Lines changed: 206 additions & 331 deletions

libensemble/alloc_funcs/give_sim_work_first.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def give_sim_work_first(
1414
alloc_specs: dict,
1515
persis_info: dict,
1616
libE_info: dict,
17-
) -> tuple[dict]:
17+
) -> tuple[dict, dict]:
1818
"""
1919
Decide what should be given to workers. This allocation function gives any
2020
available simulation work first, and only when all simulations are
@@ -41,7 +41,6 @@ def give_sim_work_first(
4141
"""
4242

4343
user = alloc_specs.get("user", {})
44-
4544
if "cancel_sims_time" in user:
4645
# Cancel simulations that are taking too long
4746
rows = np.where(np.logical_and.reduce((H["sim_started"], ~H["sim_ended"], ~H["cancel_requested"])))[0]
@@ -53,8 +52,11 @@ def give_sim_work_first(
5352
if libE_info["sim_max_given"] or not libE_info["any_idle_workers"]:
5453
return {}, persis_info
5554

56-
# Initialize alloc_specs["user"] as user.
57-
batch_give = user.get("give_all_with_same_priority", False)
55+
# Initialize options - check gen_specs first
56+
batch_give = gen_specs.get("give_all_with_same_priority", user.get("give_all_with_same_priority", False))
57+
num_active_gens = gen_specs.get("num_active_gens", user.get("num_active_gens", 1))
58+
batch_mode = gen_specs.get("batch_mode", user.get("batch_mode", False))
59+
5860
gen_in = gen_specs.get("in", [])
5961

6062
manage_resources = libE_info["use_resource_sets"]
@@ -77,11 +79,11 @@ def give_sim_work_first(
7779
else:
7880
for wid in support.avail_worker_ids(gen_workers=True):
7981
# Allow at most num_active_gens active generator instances
80-
if gen_count >= user.get("num_active_gens", gen_count + 1):
82+
if gen_count >= num_active_gens:
8183
break
8284

8385
# Do not start gen instances in batch mode if workers still working
84-
if user.get("batch_mode") and not support.all_sim_ended(H):
86+
if batch_mode and not support.all_sim_ended(H):
8587
break
8688

8789
# Give gen work

libensemble/alloc_funcs/start_only_persistent.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def only_persistent_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info, l
2525
2626
To be provided in calling script: E.g., ``alloc_specs["user"]["async_return"] = True``
2727
28-
init_sample_size: int, optional
28+
initial_batch_size: int, optional
2929
Initial sample size - always return in batch. Default: 0
3030
3131
num_active_gens: int, optional
@@ -59,16 +59,18 @@ def only_persistent_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info, l
5959
user = alloc_specs.get("user", {})
6060
manage_resources = libE_info["use_resource_sets"]
6161

62-
active_recv_gen = user.get("active_recv_gen", False) # Persistent gen can handle irregular communications
63-
init_sample_size = user.get("init_sample_size", 0) # Always batch return until this many evals complete
64-
batch_give = user.get("give_all_with_same_priority", False)
62+
active_recv_gen = gen_specs.get("active_recv_gen", user.get("active_recv_gen", False))
63+
initial_batch_size = gen_specs.get("initial_batch_size", user.get("initial_batch_size", 0))
64+
batch_give = gen_specs.get("give_all_with_same_priority", user.get("give_all_with_same_priority", False))
6565

6666
support = AllocSupport(W, manage_resources, persis_info, libE_info)
6767
gen_count = support.count_persis_gens()
6868
Work = {}
6969

7070
# Asynchronous return to generator
71-
async_return = user.get("async_return", False) and sum(H["sim_ended"]) >= init_sample_size
71+
async_return = (
72+
gen_specs.get("async_return", user.get("async_return", False)) and sum(H["sim_ended"]) >= initial_batch_size
73+
)
7274

7375
if gen_count < persis_info.get("num_gens_started", 0):
7476
# When a persistent worker is done, trigger a shutdown (returning exit condition of 1)
@@ -94,7 +96,7 @@ def only_persistent_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info, l
9496
# Now the give_sim_work_first part
9597
points_to_evaluate = ~H["sim_started"] & ~H["cancel_requested"]
9698
avail_workers = support.avail_worker_ids(persistent=False, zero_resource_workers=False, gen_workers=False)
97-
if user.get("alt_type"):
99+
if gen_specs.get("alt_type", user.get("alt_type")):
98100
avail_workers = list(
99101
set(support.avail_worker_ids(persistent=False, zero_resource_workers=False))
100102
| set(support.avail_worker_ids(persistent=EVAL_SIM_TAG, zero_resource_workers=False))
@@ -106,7 +108,7 @@ def only_persistent_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info, l
106108
sim_ids_to_send = support.points_by_priority(H, points_avail=points_to_evaluate, batch=batch_give)
107109

108110
try:
109-
if user.get("alt_type"):
111+
if gen_specs.get("alt_type", user.get("alt_type")):
110112
Work[wid] = support.sim_work(
111113
wid, H, sim_specs["in"], sim_ids_to_send, persis_info.get(wid), persistent=True
112114
)
@@ -122,7 +124,7 @@ def only_persistent_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info, l
122124
avail_workers = support.avail_worker_ids(persistent=False, zero_resource_workers=True, gen_workers=True)
123125

124126
for wid in avail_workers:
125-
if gen_count < user.get("num_active_gens", 1):
127+
if gen_count < gen_specs.get("num_active_gens", user.get("num_active_gens", 1)):
126128
# Finally, start a persistent generator as there is nothing else to do.
127129
try:
128130
Work[wid] = support.gen_work(

libensemble/gen_funcs/persistent_sampling.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
]
1717

1818

19-
def _get_user_params(user_specs):
19+
def _get_user_params(user_specs, gen_specs):
2020
"""Extract user params"""
21-
b = user_specs["initial_batch_size"]
21+
b = gen_specs.get("initial_batch_size") or user_specs.get("initial_batch_size") or gen_specs.get("init_sample_size")
2222
ub = user_specs["ub"]
2323
lb = user_specs["lb"]
2424
n = len(lb) # dimension
@@ -44,7 +44,7 @@ def persistent_uniform(_, persis_info, gen_specs, libE_info):
4444
`test_persistent_uniform_sampling_async.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_sampling_async.py>`_
4545
""" # noqa
4646

47-
b, n, lb, ub = _get_user_params(gen_specs["user"])
47+
b, n, lb, ub = _get_user_params(gen_specs.get("user", {}), gen_specs)
4848
ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
4949

5050
# Send batches until manager sends stop tag
@@ -73,7 +73,7 @@ def persistent_uniform_final_update(_, persis_info, gen_specs, libE_info):
7373
`test_persistent_uniform_sampling_running_mean.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_sampling_running_mean.py>`_
7474
""" # noqa
7575

76-
b, n, lb, ub = _get_user_params(gen_specs["user"])
76+
b, n, lb, ub = _get_user_params(gen_specs.get("user", {}), gen_specs)
7777
ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
7878

7979
def generate_corners(x, y):
@@ -145,7 +145,7 @@ def persistent_request_shutdown(_, persis_info, gen_specs, libE_info):
145145
.. seealso::
146146
`test_persistent_uniform_gen_decides_stop.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_gen_decides_stop.py>`_
147147
""" # noqa
148-
b, n, lb, ub = _get_user_params(gen_specs["user"])
148+
b, n, lb, ub = _get_user_params(gen_specs.get("user", {}), gen_specs)
149149
shutdown_limit = gen_specs["user"]["shutdown_limit"]
150150
f_count = 0
151151
ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
@@ -173,7 +173,7 @@ def uniform_nonblocking(_, persis_info, gen_specs, libE_info):
173173
.. seealso::
174174
`test_persistent_uniform_sampling.py <https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_persistent_uniform_sampling.py>`_
175175
""" # noqa
176-
b, n, lb, ub = _get_user_params(gen_specs["user"])
176+
b, n, lb, ub = _get_user_params(gen_specs.get("user", {}), gen_specs)
177177
ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
178178

179179
# Send batches until manager sends stop tag
@@ -223,7 +223,11 @@ def batched_history_matching(_, persis_info, gen_specs, libE_info):
223223
lb = gen_specs["user"]["lb"]
224224

225225
n = len(lb)
226-
b = gen_specs["user"]["initial_batch_size"]
226+
b = (
227+
gen_specs.get("initial_batch_size")
228+
or gen_specs["user"].get("initial_batch_size")
229+
or gen_specs.get("init_sample_size")
230+
)
227231
q = gen_specs["user"]["num_best_vals"]
228232
ps = PersistentSupport(libE_info, EVAL_GEN_TAG)
229233

@@ -250,7 +254,11 @@ def persistent_uniform_with_cancellations(_, persis_info, gen_specs, libE_info):
250254
ub = gen_specs["user"]["ub"]
251255
lb = gen_specs["user"]["lb"]
252256
n = len(lb)
253-
b = gen_specs["user"]["initial_batch_size"]
257+
b = (
258+
gen_specs.get("initial_batch_size")
259+
or gen_specs["user"].get("initial_batch_size")
260+
or gen_specs.get("init_sample_size")
261+
)
254262

255263
# Start cancelling points from half initial batch onward
256264
cancel_from = b // 2 # Should get at least this many points back

libensemble/specs.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,14 @@ class GenSpecs(BaseModel):
200200

201201
initial_batch_size: int = 0
202202
"""
203-
Number of initial points to request that the generator create. If zero, falls back to ``batch_size``.
204-
If both options are zero, defaults to the number of workers.
203+
Initial sample size.
204+
For standardized generators, this is the number of initial points to request that the
205+
generator create. If zero, falls back to ``batch_size``.
206+
For persistent generators, this is the number of points evaluated before switching
207+
from batch return to asynchronous return (if ``async_return`` is True).
205208
206-
Note: Certain generators included with libEnsemble decide
207-
batch sizes via ``gen_specs["user"]`` or other methods.
209+
Note: Certain generators included with libEnsemble decide batch sizes via
210+
``gen_specs["user"]`` or other methods.
208211
"""
209212

210213
batch_size: int = 0
@@ -233,6 +236,53 @@ class GenSpecs(BaseModel):
233236
they will be automatically derived from VOCS.
234237
"""
235238

239+
# Only used if using the only_persistent_gens allocation function (default)
240+
num_active_gens: int = 1
241+
"""
242+
Maximum number of persistent generators to start. Default: 1.
243+
Only used if using the ``only_persistent_gens`` allocation function (the default).
244+
"""
245+
246+
async_return: bool = False
247+
"""
248+
Return results to gen as they come in (after sample). Default: False (batch return).
249+
Only used if using the ``only_persistent_gens`` allocation function (the default).
250+
"""
251+
252+
active_recv_gen: bool = False
253+
"""
254+
Create gen in active receive mode. If True, the manager does not need to wait
255+
for a return from the generator before sending further returned points.
256+
Default: False. Only used if using the ``only_persistent_gens`` allocation function (the default).
257+
"""
258+
259+
give_all_with_same_priority: bool = False
260+
"""
261+
If True, then all points with the same priority value are given as a batch to the sim.
262+
Default: False. Only used if using the ``only_persistent_gens`` allocation function (the default).
263+
"""
264+
265+
alt_type: bool = False
266+
"""
267+
If True, then the specialized allocator behavior for some persistent gens is used.
268+
Only used if using the ``only_persistent_gens`` allocation function (the default).
269+
"""
270+
271+
batch_mode: bool = False
272+
"""
273+
If True, then the generator will not be started if there are still simulations
274+
running. Only used if using the ``give_sim_work_first`` allocation function.
275+
"""
276+
277+
@model_validator(mode="before")
278+
def set_gen_specs_fields_from_user(cls, values):
279+
"""Set fields from user dict for backward compatibility."""
280+
# init_sample_size is now initial_batch_size
281+
if "init_sample_size" in values and "initial_batch_size" not in values:
282+
values["initial_batch_size"] = values.pop("init_sample_size")
283+
284+
return values
285+
236286
@model_validator(mode="after")
237287
def set_fields_from_vocs(self):
238288
"""Set persis_in and outputs from VOCS if vocs is provided and fields are not set."""
@@ -290,10 +340,14 @@ class AllocSpecs(BaseModel):
290340
of ``give_sim_work_first``.
291341
"""
292342

293-
user: dict | None = {"num_active_gens": 1}
343+
user: dict | None = {}
294344
"""
295345
A user-data dictionary to place bounds, constants, settings, or other parameters
296346
for customizing the allocation function.
347+
348+
.. note::
349+
As of libEnsemble v2.0, generator-specific allocation options (e.g., ``async_return``,
350+
``num_active_gens``) have been moved to :class:`GenSpecs<libensemble.specs.GenSpecs>`.
297351
"""
298352

299353
outputs: list[tuple] = Field([], alias="out")

libensemble/tests/functionality_tests/test_GPU_gen_resources.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232

3333
import numpy as np
3434

35-
from libensemble.alloc_funcs.start_only_persistent import only_persistent_gens as alloc_f
3635
from libensemble.executors.mpi_executor import MPIExecutor
3736
from libensemble.gen_funcs.persistent_sampling_var_resources import uniform_sample_with_sim_gen_resources as gen_f
3837

@@ -79,23 +78,17 @@
7978
"gen_f": gen_f,
8079
"persis_in": ["f", "x", "sim_id"],
8180
"out": [("num_procs", int), ("num_gpus", int), ("x", float, n)],
81+
"initial_batch_size": nworkers - 1,
82+
"give_all_with_same_priority": False,
83+
"async_return": False,
8284
"user": {
83-
"initial_batch_size": nworkers - 1,
8485
"max_procs": nworkers - 1, # Any sim created can req. 1 worker up to all.
8586
"lb": np.array([-3, -2]),
8687
"ub": np.array([3, 2]),
8788
"dry_run": dry_run,
8889
},
8990
}
9091

91-
alloc_specs = {
92-
"alloc_f": alloc_f,
93-
"user": {
94-
"give_all_with_same_priority": False,
95-
"async_return": False, # False batch returns
96-
},
97-
}
98-
9992
exit_criteria = {"sim_max": 20}
10093
libE_specs["resource_info"] = {"cores_on_node": (nworkers * 2, nworkers * 4), "gpus_on_node": nworkers}
10194

@@ -126,8 +119,6 @@
126119
gen_specs["user"]["max_procs"] = max(nworkers - 2, 1)
127120

128121
# Perform the run
129-
H, persis_info, flag = libE(
130-
sim_specs, gen_specs, exit_criteria, persis_info, libE_specs=libE_specs, alloc_specs=alloc_specs
131-
)
122+
H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, libE_specs=libE_specs)
132123

133124
# All asserts are in gen and sim funcs

libensemble/tests/functionality_tests/test_asktell_sampling.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import libensemble.sim_funcs.six_hump_camel as six_hump_camel
2020

2121
# Import libEnsemble items for this test
22-
from libensemble.alloc_funcs.start_only_persistent import only_persistent_gens as alloc_f
2322
from libensemble.gen_classes.sampling import UniformSample
2423
from libensemble.libE import libE
2524
from libensemble.sim_funcs.executor_hworld import executor_hworld as sim_f_exec
@@ -59,7 +58,6 @@ def sim_f(In):
5958

6059
vocs = VOCS(variables=variables, objectives=objectives)
6160

62-
alloc_specs = {"alloc_f": alloc_f}
6361
exit_criteria = {"gen_max": 201}
6462
persis_info = add_unique_random_streams({}, nworkers + 1, seed=1234)
6563

@@ -89,9 +87,7 @@ def sim_f(In):
8987
}
9088

9189
gen_specs["generator"] = generator
92-
H, persis_info, flag = libE(
93-
sim_specs, gen_specs, exit_criteria, persis_info, alloc_specs, libE_specs=libE_specs
94-
)
90+
H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, persis_info, libE_specs=libE_specs)
9591

9692
if is_manager:
9793
print(H[["sim_id", "x", "f"]][:10])

libensemble/tests/functionality_tests/test_asktell_sampling_external_gen.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@
2020

2121
from libensemble import Ensemble
2222

23-
# Import libEnsemble items for this test
24-
from libensemble.alloc_funcs.start_only_persistent import only_persistent_gens as alloc_f
25-
2623
# from libensemble.gen_classes.external.sampling import UniformSampleArray
2724
from libensemble.gen_classes.external.sampling import UniformSample
28-
from libensemble.specs import AllocSpecs, ExitCriteria, GenSpecs, LibeSpecs, SimSpecs
25+
from libensemble.specs import ExitCriteria, GenSpecs, LibeSpecs, SimSpecs
26+
27+
# Import libEnsemble items for this test
28+
2929

3030
# from gest_api.vocs import ContinuousVariable
3131

@@ -75,15 +75,13 @@ def sim_f_scalar(In):
7575
vocs=vocs,
7676
)
7777

78-
alloc_specs = AllocSpecs(alloc_f=alloc_f)
7978
exit_criteria = ExitCriteria(gen_max=201)
8079

8180
ensemble = Ensemble(
8281
parse_args=True,
8382
sim_specs=sim_specs,
8483
gen_specs=gen_specs,
8584
exit_criteria=exit_criteria,
86-
alloc_specs=alloc_specs,
8785
libE_specs=libE_specs,
8886
)
8987

0 commit comments

Comments
 (0)