From d6f3bc373bca9b163bd96599873da2e391f1e127 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 4 Jun 2026 12:01:55 -0500 Subject: [PATCH 1/3] experiments with deprecation policies applied towards older allocs --- AGENTS.md | 72 ++++++++++++++ docs/function_guides/allocator.rst | 93 ++++++++++++++----- libensemble/alloc_funcs/fast_alloc.py | 12 +++ .../alloc_funcs/fast_alloc_and_pausing.py | 11 +++ .../alloc_funcs/inverse_bayes_allocf.py | 11 +++ libensemble/alloc_funcs/only_one_gen_alloc.py | 12 +++ .../alloc_funcs/start_fd_persistent.py | 11 +++ .../start_persistent_local_opt_gens.py | 11 +++ libensemble/gen_classes/__init__.py | 1 + pyproject.toml | 7 ++ 10 files changed, 217 insertions(+), 24 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f5673f64a7..d2b817631f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -99,3 +99,75 @@ When modernizing existing libEnsemble scripts (functionality tests, regression t - **Mandatory Fields**: Ensure `gen_specs["in"]` or `gen_specs["persis_in"]` includes at least one field (e.g., `["sim_id"]`) if feedback is sent back to the generator, to satisfy the allocator's requirements. - **gest-api Simulators**: The gest-api pattern also applies to simulators. Set `SimSpecs.simulator` to a callable with signature `(input_dict: dict, **kwargs) -> dict` instead of providing a `sim_f`. libEnsemble automatically wraps it with `gest_api_sim` from `libensemble.sim_funcs.gest_api_wrapper` and handles all NumPy conversions. `SimSpecs.inputs` and `SimSpecs.outputs` can be derived automatically when `SimSpecs.vocs` is provided. - **`safe_mode` is opt-in**: `libE_specs["safe_mode"]` defaults to `False`, meaning protected History fields (`gen_worker`, `gen_started_time`, `gen_ended_time`, `sim_worker`, `sim_started`, `sim_started_time`, `sim_ended`, `sim_ended_time`, `gen_informed`, `gen_informed_time`, `kill_sent`) are freely overwritable by default. Set `safe_mode=True` to enable protection. Overwriting these fields without understanding their purpose may crash libEnsemble. +- **Pre-generated samples**: Scripts that previously used the ``give_pregenerated_work`` allocator (with no generator) should be migrated to use ``PreloadedSampleGenerator`` from ``libensemble.gen_classes.preloaded``. Pass it as ``GenSpecs(generator=PreloadedSampleGenerator(H0))`` and use the default ``AllocSpecs()``. The generator serves the pre-loaded points via ``suggest()`` and returns an empty list when exhausted, triggering normal ensemble shutdown. + +Deprecation Policy +------------------ + +This section describes the standard process for deprecating **any** libEnsemble feature +(allocation functions, generator classes, public API, parameters, etc.). + +**Warning category** + +Always use ``LibEnsembleDeprecationWarning`` — a custom subclass of ``DeprecationWarning`` +importable from ``libensemble._deprecation``. Never emit bare ``DeprecationWarning`` +directly. The custom subclass lets users and downstream libraries filter libEnsemble +deprecations independently:: + + from libensemble._deprecation import LibEnsembleDeprecationWarning + warnings.filterwarnings("error", category=LibEnsembleDeprecationWarning) + +**Emit the warning** + +Emit the warning at the earliest point of use (module import, class instantiation, or +function call — whichever the user is most likely to see). Use the ``warn_deprecated()`` +helper from the same module when the standard message format is sufficient:: + + import warnings + from libensemble._deprecation import LibEnsembleDeprecationWarning + + warnings.warn( + "libensemble.. is deprecated as of libEnsemble X.Y " + "and will be removed in X.Z. Use instead. " + "See https://libensemble.readthedocs.io/... for migration guidance.", + LibEnsembleDeprecationWarning, + stacklevel=2, # points to the caller's import/call site + ) + +**Docstring banner** + +Add a ``.. deprecated:: X.Y`` directive at the top of the deprecated object's docstring, +naming the replacement and the removal version:: + + def my_old_function(...): + """ + .. deprecated:: 2.0 + ``my_old_function`` is deprecated and will be removed in libEnsemble 2.1. + Use :func:`libensemble.module.my_new_function` instead. + ... + """ + +**Sphinx docs** + +In the relevant ``.rst`` file, move the deprecated item to a "Deprecated" section (or +subsection) at the bottom of the page and prefix its ``automodule``/``autofunction`` block +with a ``.. deprecated:: X.Y`` admonition and a ``.. warning::`` summarising all items +in the section together with migration guidance. See +``docs/function_guides/allocator.rst`` for a reference example. + +**Pytest noise suppression** + +Add a ``filterwarnings`` rule to ``[tool.pytest.ini_options]`` in ``pyproject.toml`` so +that tests of deprecated-but-not-yet-removed code do not produce noisy output:: + + [tool.pytest.ini_options] + filterwarnings = [ + "ignore::libensemble._deprecation.LibEnsembleDeprecationWarning", + ] + +Remove this rule when the deprecated code is deleted. + +**Timeline** + +The standard window is: soft-deprecate in release N, hard-remove (delete code + tests) in N+1. +Tests that exclusively cover deprecated features are deleted in the removal release, not before. diff --git a/docs/function_guides/allocator.rst b/docs/function_guides/allocator.rst index 7a04f2f783..980f0c822c 100644 --- a/docs/function_guides/allocator.rst +++ b/docs/function_guides/allocator.rst @@ -15,8 +15,8 @@ We encourage experimenting with: .. dropdown:: Example - .. literalinclude:: ../../libensemble/alloc_funcs/fast_alloc.py - :caption: libensemble.alloc_funcs.fast_alloc.give_sim_work_first + .. literalinclude:: ../../libensemble/alloc_funcs/give_sim_work_first.py + :caption: libensemble.alloc_funcs.give_sim_work_first.give_sim_work_first The ``alloc_f`` function definition resembles:: @@ -184,56 +184,101 @@ give_sim_work_first :language: python :linenos: -fast_alloc ----------- -.. automodule:: fast_alloc - :members: - :undoc-members: +persistent_aposmm_alloc +----------------------- +.. automodule:: persistent_aposmm_alloc + :members: + :undoc-members: + +give_pregenerated_work +---------------------- +.. automodule:: give_pregenerated_work + :members: + :undoc-members: -.. dropdown:: :underline:`fast_alloc.py` +.. _deprecated-alloc-label: - .. literalinclude:: ../../libensemble/alloc_funcs/fast_alloc.py - :language: python - :linenos: +Deprecated Allocation Functions +================================ -start_persistent_local_opt_gens -------------------------------- -.. automodule:: start_persistent_local_opt_gens +.. warning:: + + The following allocation functions are **deprecated as of libEnsemble 2.0** and will be + **removed in libEnsemble 2.1**. They emit a :class:`~libensemble._deprecation.LibEnsembleDeprecationWarning` + on import. + + **Migration guidance:** + + - Functions that managed non-persistent generators (``fast_alloc``, ``fast_alloc_and_pausing``, + ``only_one_gen_alloc``) should be replaced with + :func:`~libensemble.alloc_funcs.give_sim_work_first.give_sim_work_first` or the default + :func:`~libensemble.alloc_funcs.start_only_persistent.only_persistent_gens` with a + persistent generator. + - APOSMM-adjacent functions (``start_persistent_local_opt_gens``, ``start_fd_persistent``) + should migrate to + :func:`~libensemble.alloc_funcs.persistent_aposmm_alloc.persistent_aposmm_alloc`. + - ``inverse_bayes_allocf`` should be replaced with the default ``only_persistent_gens`` + combined with a persistent generator that implements the required batch/subbatch logic. + +fast_alloc +---------- +.. deprecated:: 2.0 + Use :func:`~libensemble.alloc_funcs.give_sim_work_first.give_sim_work_first` or the default + :func:`~libensemble.alloc_funcs.start_only_persistent.only_persistent_gens` instead. + Will be removed in libEnsemble 2.1. + +.. automodule:: fast_alloc :members: :undoc-members: fast_alloc_and_pausing ---------------------- +.. deprecated:: 2.0 + Use the default :func:`~libensemble.alloc_funcs.start_only_persistent.only_persistent_gens` + with a persistent generator instead. Will be removed in libEnsemble 2.1. + .. automodule:: fast_alloc_and_pausing :members: :undoc-members: only_one_gen_alloc ------------------ +.. deprecated:: 2.0 + Use :func:`~libensemble.alloc_funcs.give_sim_work_first.give_sim_work_first` with + ``num_active_gens=1``, or the default + :func:`~libensemble.alloc_funcs.start_only_persistent.only_persistent_gens` instead. + Will be removed in libEnsemble 2.1. + .. automodule:: only_one_gen_alloc :members: :undoc-members: start_fd_persistent ------------------- +.. deprecated:: 2.0 + Use the default :func:`~libensemble.alloc_funcs.start_only_persistent.only_persistent_gens` + with a persistent generator instead. Will be removed in libEnsemble 2.1. + .. automodule:: start_fd_persistent :members: :undoc-members: -persistent_aposmm_alloc ------------------------ -.. automodule:: persistent_aposmm_alloc - :members: - :undoc-members: +start_persistent_local_opt_gens +------------------------------- +.. deprecated:: 2.0 + Use :func:`~libensemble.alloc_funcs.persistent_aposmm_alloc.persistent_aposmm_alloc` + instead. Will be removed in libEnsemble 2.1. -give_pregenerated_work ----------------------- -.. automodule:: give_pregenerated_work - :members: - :undoc-members: +.. automodule:: start_persistent_local_opt_gens + :members: + :undoc-members: inverse_bayes_allocf -------------------- +.. deprecated:: 2.0 + Use the default :func:`~libensemble.alloc_funcs.start_only_persistent.only_persistent_gens` + with a persistent generator instead. Will be removed in libEnsemble 2.1. + .. automodule:: inverse_bayes_allocf :members: :undoc-members: diff --git a/libensemble/alloc_funcs/fast_alloc.py b/libensemble/alloc_funcs/fast_alloc.py index e2027da1c0..f55a9464ac 100644 --- a/libensemble/alloc_funcs/fast_alloc.py +++ b/libensemble/alloc_funcs/fast_alloc.py @@ -1,8 +1,20 @@ +from libensemble._deprecation import warn_deprecated from libensemble.tools.alloc_support import AllocSupport, InsufficientFreeResources +warn_deprecated( + name="libensemble.alloc_funcs.fast_alloc", + replacement="libensemble.alloc_funcs.give_sim_work_first.give_sim_work_first " + "or the default only_persistent_gens with a persistent generator", +) + def give_sim_work_first(W, H, sim_specs, gen_specs, alloc_specs, persis_info, libE_info): """ + .. deprecated:: 2.0 + ``fast_alloc.give_sim_work_first`` is deprecated and will be removed in libEnsemble 2.1. + Use :func:`libensemble.alloc_funcs.give_sim_work_first.give_sim_work_first` or the + default ``only_persistent_gens`` (with a persistent generator) instead. + This allocation function gives (in order) entries in ``H`` to idle workers to evaluate in the simulation function. The fields in ``sim_specs["in"]`` are given. If all entries in `H` have been given a be evaluated, a worker diff --git a/libensemble/alloc_funcs/fast_alloc_and_pausing.py b/libensemble/alloc_funcs/fast_alloc_and_pausing.py index fd162a6623..a7a3b84424 100644 --- a/libensemble/alloc_funcs/fast_alloc_and_pausing.py +++ b/libensemble/alloc_funcs/fast_alloc_and_pausing.py @@ -1,10 +1,21 @@ import numpy as np +from libensemble._deprecation import warn_deprecated from libensemble.tools.alloc_support import AllocSupport, InsufficientFreeResources +warn_deprecated( + name="libensemble.alloc_funcs.fast_alloc_and_pausing", + replacement="the default only_persistent_gens with a persistent generator", +) + def give_sim_work_first(W, H, sim_specs, gen_specs, alloc_specs, persis_info, libE_info): """ + .. deprecated:: 2.0 + ``fast_alloc_and_pausing.give_sim_work_first`` is deprecated and will be removed in + libEnsemble 2.1. Use the default ``only_persistent_gens`` (with a persistent generator) + instead. + This allocation function gives (in order) entries in ``H`` to idle workers to evaluate in the simulation function. The fields in ``sim_specs["in"]`` are given. If all entries in `H` have been given a be evaluated, a worker diff --git a/libensemble/alloc_funcs/inverse_bayes_allocf.py b/libensemble/alloc_funcs/inverse_bayes_allocf.py index e0521df6ff..2daf3e84b2 100644 --- a/libensemble/alloc_funcs/inverse_bayes_allocf.py +++ b/libensemble/alloc_funcs/inverse_bayes_allocf.py @@ -1,11 +1,22 @@ import numpy as np +from libensemble._deprecation import warn_deprecated from libensemble.message_numbers import EVAL_GEN_TAG from libensemble.tools.alloc_support import AllocSupport, InsufficientFreeResources +warn_deprecated( + name="libensemble.alloc_funcs.inverse_bayes_allocf", + replacement="the default only_persistent_gens with a persistent generator", +) + def only_persistent_gens_for_inverse_bayes(W, H, sim_specs, gen_specs, alloc_specs, persis_info, libE_info): """ + .. deprecated:: 2.0 + ``inverse_bayes_allocf.only_persistent_gens_for_inverse_bayes`` is deprecated and will + be removed in libEnsemble 2.1. Use the default ``only_persistent_gens`` (with a + persistent generator) instead. + Starts up to gen_count number of persistent generators. These persistent generators produce points (x) in batches and subbatches. The points x are given in subbatches to workers to perform a calculation. diff --git a/libensemble/alloc_funcs/only_one_gen_alloc.py b/libensemble/alloc_funcs/only_one_gen_alloc.py index 7eb6a91e0b..b00c1296bc 100644 --- a/libensemble/alloc_funcs/only_one_gen_alloc.py +++ b/libensemble/alloc_funcs/only_one_gen_alloc.py @@ -1,8 +1,20 @@ +from libensemble._deprecation import warn_deprecated from libensemble.tools.alloc_support import AllocSupport, InsufficientFreeResources +warn_deprecated( + name="libensemble.alloc_funcs.only_one_gen_alloc", + replacement="libensemble.alloc_funcs.give_sim_work_first.give_sim_work_first " + "with num_active_gens=1, or the default only_persistent_gens", +) + def ensure_one_active_gen(W, H, sim_specs, gen_specs, alloc_specs, persis_info, libE_info): """ + .. deprecated:: 2.0 + ``only_one_gen_alloc.ensure_one_active_gen`` is deprecated and will be removed in + libEnsemble 2.1. Use :func:`libensemble.alloc_funcs.give_sim_work_first.give_sim_work_first` + with ``num_active_gens=1``, or the default ``only_persistent_gens`` instead. + This allocation function gives (in order) entries in ``H`` to idle workers to evaluate in the simulation function. The fields in ``sim_specs["in"]`` are given. If there is no active generator, then one is started. diff --git a/libensemble/alloc_funcs/start_fd_persistent.py b/libensemble/alloc_funcs/start_fd_persistent.py index 36fba0a730..56f4155894 100644 --- a/libensemble/alloc_funcs/start_fd_persistent.py +++ b/libensemble/alloc_funcs/start_fd_persistent.py @@ -1,11 +1,22 @@ import numpy as np +from libensemble._deprecation import warn_deprecated from libensemble.message_numbers import EVAL_GEN_TAG from libensemble.tools.alloc_support import AllocSupport, InsufficientFreeResources +warn_deprecated( + name="libensemble.alloc_funcs.start_fd_persistent", + replacement="the default only_persistent_gens with a persistent generator", +) + def finite_diff_alloc(W, H, sim_specs, gen_specs, alloc_specs, persis_info, libE_info): """ + .. deprecated:: 2.0 + ``start_fd_persistent.finite_diff_alloc`` is deprecated and will be removed in + libEnsemble 2.1. Use the default ``only_persistent_gens`` (with a persistent generator) + instead. + This allocation function will give simulation work if possible, but otherwise start 1 persistent generator. If all points requested by the persistent generator for a given (x_ind, f_ind) pair have been returned from the diff --git a/libensemble/alloc_funcs/start_persistent_local_opt_gens.py b/libensemble/alloc_funcs/start_persistent_local_opt_gens.py index 9f0537b8e3..0e7ff2f57d 100644 --- a/libensemble/alloc_funcs/start_persistent_local_opt_gens.py +++ b/libensemble/alloc_funcs/start_persistent_local_opt_gens.py @@ -1,12 +1,23 @@ import numpy as np +from libensemble._deprecation import warn_deprecated from libensemble.gen_funcs.persistent_aposmm import decide_where_to_start_localopt, extract_rk_c, update_history_dist from libensemble.message_numbers import EVAL_GEN_TAG from libensemble.tools.alloc_support import AllocSupport, InsufficientFreeResources +warn_deprecated( + name="libensemble.alloc_funcs.start_persistent_local_opt_gens", + replacement="libensemble.alloc_funcs.persistent_aposmm_alloc.persistent_aposmm_alloc", +) + def start_persistent_local_opt_gens(W, H, sim_specs, gen_specs, alloc_specs, persis_info, libE_info): """ + .. deprecated:: 2.0 + ``start_persistent_local_opt_gens.start_persistent_local_opt_gens`` is deprecated and + will be removed in libEnsemble 2.1. Use + :func:`libensemble.alloc_funcs.persistent_aposmm_alloc.persistent_aposmm_alloc` instead. + This allocation function will do the following: - Start up a persistent generator that is a local opt run at the first point diff --git a/libensemble/gen_classes/__init__.py b/libensemble/gen_classes/__init__.py index d0524159da..339e192102 100644 --- a/libensemble/gen_classes/__init__.py +++ b/libensemble/gen_classes/__init__.py @@ -1,2 +1,3 @@ from .aposmm import APOSMM # noqa: F401 +from .preloaded import PreloadedSampleGenerator # noqa: F401 from .sampling import UniformSample # noqa: F401 diff --git a/pyproject.toml b/pyproject.toml index fbb96f22da..7ad08176e4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -217,6 +217,13 @@ dev = ["wat>=0.7.0,<0.8"] docs = ["pyenchant", "enchant>=0.0.1,<0.0.2", "sphinx-lfs-content>=1.1.10,<2"] # Various config from here onward +[tool.pytest.ini_options] +filterwarnings = [ + # Suppress deprecation warnings from alloc modules being removed in 2.1. + # Their tests are deleted in 2.1; until then, silence the import-time noise. + "ignore::libensemble._deprecation.LibEnsembleDeprecationWarning", +] + [tool.black] line-length = 120 target-version = ["py311", "py312", "py313", "py314"] From a6fd18a16643afa62fe59183d2ca4970b0dae701 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 5 Jun 2026 11:51:21 -0500 Subject: [PATCH 2/3] adds _deprecation module. Adds PreloadedGenerator class for existing work without needing a corresponding alloc --- libensemble/_deprecation.py | 38 ++++++ libensemble/gen_classes/preloaded.py | 175 +++++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 libensemble/_deprecation.py create mode 100644 libensemble/gen_classes/preloaded.py diff --git a/libensemble/_deprecation.py b/libensemble/_deprecation.py new file mode 100644 index 0000000000..4606bb06dc --- /dev/null +++ b/libensemble/_deprecation.py @@ -0,0 +1,38 @@ +""" +Deprecation utilities for libEnsemble. +""" + +import warnings + + +class LibEnsembleDeprecationWarning(DeprecationWarning): + """Warning category for deprecated libEnsemble features. + + Subclass of :class:`DeprecationWarning` so users can filter libEnsemble + deprecations independently:: + + import warnings + from libensemble._deprecation import LibEnsembleDeprecationWarning + warnings.filterwarnings("error", category=LibEnsembleDeprecationWarning) + """ + + +def warn_deprecated(name: str, replacement: str, removal_version: str = "2.1") -> None: + """Emit a :class:`LibEnsembleDeprecationWarning` for a deprecated feature. + + Parameters + ---------- + name: + Dotted module or object path (e.g. ``"libensemble.alloc_funcs.fast_alloc"``). + replacement: + Human-readable description of the recommended replacement. + removal_version: + The libEnsemble version in which the feature will be removed. + """ + warnings.warn( + f"{name} is deprecated as of libEnsemble 2.0 " + f"and will be removed in {removal_version}. " + f"Use {replacement} instead.", + LibEnsembleDeprecationWarning, + stacklevel=3, + ) diff --git a/libensemble/gen_classes/preloaded.py b/libensemble/gen_classes/preloaded.py new file mode 100644 index 0000000000..d032e205e4 --- /dev/null +++ b/libensemble/gen_classes/preloaded.py @@ -0,0 +1,175 @@ +"""Generator class for evaluating a pre-existing sample of points. + +This module provides :class:`PreloadedSampleGenerator`, a gest-api compatible +generator that wraps a user-supplied set of points and serves them to libEnsemble +workers for simulation evaluation. No VOCS or online generation is required. + +Typical usage:: + + import numpy as np + from libensemble import Ensemble + from libensemble.gen_classes.preloaded import PreloadedSampleGenerator + from libensemble.specs import ExitCriteria, GenSpecs, SimSpecs + + H0 = np.zeros(500, dtype=[("x", float, 8), ("sim_id", int)]) + H0["x"] = my_existing_points + H0["sim_id"] = range(500) + + sampling = Ensemble(parse_args=True) + sampling.gen_specs = GenSpecs(generator=PreloadedSampleGenerator(H0)) + sampling.sim_specs = SimSpecs(sim_f=my_sim, inputs=["x"], out=[("f", float)]) + sampling.exit_criteria = ExitCriteria(sim_max=len(H0)) + sampling.run() + +This replaces the legacy ``give_pregenerated_work`` allocator pattern, which +required a custom ``AllocSpecs`` and bypassed the generator entirely. With +:class:`PreloadedSampleGenerator` the default ``only_persistent_gens`` allocator +is used transparently. +""" + +from typing import List, Optional, Union + +import numpy as np +import numpy.typing as npt +from gest_api import Generator +from gest_api.vocs import VOCS + +from libensemble.utils.misc import np_to_list_dicts + +__all__ = ["PreloadedSampleGenerator"] + +# Sentinel VOCS used when the caller does not supply one. The generator never +# actually samples from this; it is required only to satisfy the gest-api +# Generator base class constructor. The list form ``[low, high]`` is the +# canonical gest-api shorthand for a continuous variable. +_SENTINEL_VOCS = VOCS(variables={"_preloaded": [0.0, 1.0]}) + + +class PreloadedSampleGenerator(Generator): + """A gest-api generator that serves a fixed, pre-existing set of points. + + Points are taken from a numpy structured array (or a list of dicts) supplied + at construction time and returned to libEnsemble in chunks via :meth:`suggest`. + Once all points are exhausted :meth:`suggest` returns an empty list, which + causes the default ``only_persistent_gens`` allocator to shut down the + generator and end the ensemble. + + No online learning or VOCS sampling is performed; :meth:`ingest` is a no-op. + + Parameters + ---------- + points: + Pre-generated points to evaluate. May be either: + + * A numpy structured array whose field names match ``sim_specs["in"]``. + Fields ``sim_id`` and ``sim_started`` are ignored (libEnsemble manages + them internally). + * A list of dicts with consistent keys. + vocs: + Optional VOCS object. If omitted a sentinel placeholder is used — the + generator does not sample from it. + batch_size: + Number of points to return per :meth:`suggest` call. Defaults to + returning all remaining points at once (i.e. one large batch). Setting + this to a positive integer enables streaming delivery, which can reduce + peak memory pressure for very large pre-generated samples. + + Examples + -------- + Evaluate 1000 pre-generated borehole inputs: + + .. code-block:: python + + import numpy as np + from libensemble import Ensemble + from libensemble.gen_classes.preloaded import PreloadedSampleGenerator + from libensemble.sim_funcs.borehole import borehole as sim_f, gen_borehole_input + from libensemble.specs import ExitCriteria, GenSpecs, SimSpecs + + n_samp = 1000 + pts = np.zeros(n_samp, dtype=[("x", float, 8)]) + pts["x"] = gen_borehole_input(n_samp) + + sampling = Ensemble(parse_args=True) + sampling.gen_specs = GenSpecs(generator=PreloadedSampleGenerator(pts)) + sampling.sim_specs = SimSpecs(sim_f=sim_f, inputs=["x"], out=[("f", float)]) + sampling.exit_criteria = ExitCriteria(sim_max=n_samp) + sampling.run() + """ + + def __init__( + self, + points: Union[npt.NDArray, List[dict]], + vocs: Optional[VOCS] = None, + batch_size: Optional[int] = None, + ) -> None: + super().__init__(vocs if vocs is not None else _SENTINEL_VOCS) + + # Normalise to a list-of-dicts for the gest-api suggest() return type. + if isinstance(points, np.ndarray): + # Strip fields that libEnsemble manages internally before converting. + internal = {"sim_id", "sim_started", "sim_ended", "gen_worker"} + user_fields = [n for n in points.dtype.names if n not in internal] + self._points: List[dict] = np_to_list_dicts(points[user_fields]) + else: + self._points = list(points) + + if batch_size is not None and batch_size <= 0: + raise ValueError(f"batch_size must be a positive integer, got {batch_size!r}") + self._batch_size = batch_size + self._cursor: int = 0 + + # ------------------------------------------------------------------ + # gest-api interface + # ------------------------------------------------------------------ + + def _validate_vocs(self, vocs: VOCS) -> None: # noqa: D102 + pass # No VOCS constraints — pre-loaded points already exist. + + def suggest(self, n_trials: int) -> List[dict]: + """Return the next batch of pre-loaded points. + + Parameters + ---------- + n_trials: + Hint from the allocator for how many points are needed. When + ``batch_size`` was set at construction that value takes precedence; + otherwise ``n_trials`` is honoured. + + Returns + ------- + list[dict] + Next chunk of points, or ``[]`` when all points have been served. + """ + if self._cursor >= len(self._points): + return [] + + # Determine how many points to emit this call. + chunk = self._batch_size if self._batch_size is not None else n_trials + # Never return more than what is left. + chunk = min(chunk, len(self._points) - self._cursor) + + batch = self._points[self._cursor : self._cursor + chunk] + self._cursor += chunk + return batch + + def ingest(self, calc_in: List[dict]) -> None: # noqa: D102 — intentional no-op + """Receive simulation results (no-op — pre-loaded sample needs no feedback).""" + + # ------------------------------------------------------------------ + # Convenience + # ------------------------------------------------------------------ + + @property + def n_remaining(self) -> int: + """Number of points not yet served by :meth:`suggest`.""" + return max(0, len(self._points) - self._cursor) + + def __repr__(self) -> str: # pragma: no cover + return ( + f"{self.__class__.__name__}(" + f"total={len(self._points)}, " + f"served={self._cursor}, " + f"remaining={self.n_remaining}, " + f"batch_size={self._batch_size!r})" + ) From c1e1345161d9b592dd13eea85b0359b8816ab9a3 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 11 Jun 2026 15:55:32 -0500 Subject: [PATCH 3/3] run_tests.py ignores deprecationwarnings --- libensemble/tests/run_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libensemble/tests/run_tests.py b/libensemble/tests/run_tests.py index e566d451f5..31798c7f11 100755 --- a/libensemble/tests/run_tests.py +++ b/libensemble/tests/run_tests.py @@ -303,7 +303,7 @@ def skip_config(directives, args, comm): def make_run_line(python_exec, test_script, comm, nprocs, args): """Build run line""" - cmd = python_exec + cov_opts + [test_script] + cmd = python_exec + ["-W", "ignore::DeprecationWarning"] + cov_opts + [test_script] if comm == "mpi": cmd = ["mpiexec", "-np", str(nprocs)] + (args.a.split() if args.a else []) + cmd else: