From 57ad733637191c7a4d36f5c38424b70f0e0782a0 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 7 Jan 2026 15:02:40 -0600 Subject: [PATCH 01/13] adjust README intro sample to use standard gen, VOCS --- README.rst | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/README.rst b/README.rst index 246eba14b..9299a46ff 100644 --- a/README.rst +++ b/README.rst @@ -44,8 +44,10 @@ and an exit condition. Run the following four-worker example via ``python this_f import numpy as np + from gest_api.vocs import VOCS + from libensemble import Ensemble - from libensemble.gen_funcs.sampling import uniform_random_sample + from libensemble.gen_classes.sampling import UniformSample from libensemble.sim_funcs.six_hump_camel import six_hump_camel from libensemble.specs import ExitCriteria, GenSpecs, LibeSpecs, SimSpecs @@ -53,37 +55,41 @@ and an exit condition. Run the following four-worker example via ``python this_f libE_specs = LibeSpecs(nworkers=4) + vocs = VOCS( + variables={ + "x0": [-3, 3], + "x1": [-2, 2], + }, + objectives={"f": "EXPLORE"}, + ) + + generator = UniformSample(vocs) + sim_specs = SimSpecs( sim_f=six_hump_camel, - inputs=["x"], - outputs=[("f", float)], + vocs=vocs, ) gen_specs = GenSpecs( - gen_f=uniform_random_sample, - outputs=[("x", float, 2)], - user={ - "gen_batch_size": 50, - "lb": np.array([-3, -2]), - "ub": np.array([3, 2]), - }, + generator=generator, + vocs=vocs, ) exit_criteria = ExitCriteria(sim_max=100) - sampling = Ensemble( + ensemble = Ensemble( libE_specs=libE_specs, sim_specs=sim_specs, gen_specs=gen_specs, exit_criteria=exit_criteria, ) - sampling.add_random_streams() - sampling.run() + ensemble.add_random_streams() + ensemble.run() - if sampling.is_manager: - sampling.save_output(__file__) - print("Some output data:\n", sampling.H[["x", "f"]][:10]) + if ensemble.is_manager: + ensemble.save_output(__file__) + print("Some output data:\n", ensemble.H[["x", "f"]][:10]) |Inline Example| From c2bf534f240d5b983b593f672349036506d51a55 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 8 Jan 2026 11:03:55 -0600 Subject: [PATCH 02/13] de-emphasize the "user-functions" in favor of generators and simulators. separate out the allocator discussion --- docs/overview_usecases.rst | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/overview_usecases.rst b/docs/overview_usecases.rst index 6d77b197b..2effcf8fe 100644 --- a/docs/overview_usecases.rst +++ b/docs/overview_usecases.rst @@ -1,17 +1,15 @@ Understanding libEnsemble ========================= -Manager, Workers, and User Functions -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Manager, Workers, Generators, and Simulators +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. begin_overview_rst_tag libEnsemble's **manager** allocates work to **workers**, -which perform computations via **user functions**: +which perform computations via **generators** and **simulators**: -* :ref:`generator`: Generates inputs to the *simulator* (``sim_f``) -* :ref:`simulator`: Performs an evaluation based on parameters from the *generator* (``gen_f``) -* :ref:`allocator`: Decides whether a simulator or generator should be - called (and with what inputs/resources) as workers become available +* :ref:`generator`: Generates inputs to the *simulator* +* :ref:`simulator`: Performs an evaluation based on parameters from the *generator* .. figure:: images/adaptiveloop.png :alt: Adaptive loops @@ -20,10 +18,6 @@ which perform computations via **user functions**: | -The default allocator (``alloc_f``) instructs workers to run the simulator on the -highest priority work from the generator. If a worker is idle and there is -no work, that worker is instructed to call the generator. - .. figure:: images/diagram_with_persis.png :alt: libE component diagram :align: center @@ -31,12 +25,23 @@ no work, that worker is instructed to call the generator. | -An :doc:`executor` interface is available so user functions -can execute and monitor external applications. +An :doc:`executor` interface is available so generators and simulators +can launch and monitor external applications. libEnsemble uses a NumPy structured array known as the :ref:`history array` -to keep a record of all simulations. The global history array is stored on the -manager, while selected rows and fields of this array are passed to and from user functions. +to keep a record of all simulations and generated values. + +Allocator Function +~~~~~~~~~~~~~~~~~~ + +* :ref:`allocator`: Decides whether a simulator or generator should be + prompted (and with what inputs/resources) as workers become available + +The default allocator (``alloc_f``) prompts workers to run the highest priority simulator work. +If a worker is idle and there is no simulator work, that worker is prompted to query the generator. + +The default allocator is appropriate for the vast majority of use-cases, but is customizable +for users interested in more advanced allocation strategies. Example Use Cases ~~~~~~~~~~~~~~~~~ From 10d081081a0158185b66e2dfe25636431384b787 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 8 Jan 2026 13:44:27 -0600 Subject: [PATCH 03/13] mention uv as an install approach. in README example vocs->variables_objectives for clarity. remove libE() doc --- README.rst | 8 ++++---- docs/advanced_installation.rst | 15 +++++++++------ docs/libe_module.rst | 19 +++---------------- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/README.rst b/README.rst index 9299a46ff..acde5faff 100644 --- a/README.rst +++ b/README.rst @@ -55,7 +55,7 @@ and an exit condition. Run the following four-worker example via ``python this_f libE_specs = LibeSpecs(nworkers=4) - vocs = VOCS( + variables_objectives = VOCS( variables={ "x0": [-3, 3], "x1": [-2, 2], @@ -63,16 +63,16 @@ and an exit condition. Run the following four-worker example via ``python this_f objectives={"f": "EXPLORE"}, ) - generator = UniformSample(vocs) + generator = UniformSample(vocs=variables_objectives) sim_specs = SimSpecs( sim_f=six_hump_camel, - vocs=vocs, + vocs=variables_objectives, ) gen_specs = GenSpecs( generator=generator, - vocs=vocs, + vocs=variables_objectives, ) exit_criteria = ExitCriteria(sim_max=100) diff --git a/docs/advanced_installation.rst b/docs/advanced_installation.rst index c02a63ed6..1ee5f95e7 100644 --- a/docs/advanced_installation.rst +++ b/docs/advanced_installation.rst @@ -1,7 +1,7 @@ Advanced Installation ===================== -libEnsemble can be installed from ``pip``, ``Conda``, or ``Spack``. +libEnsemble can be installed from ``pip``, ``uv``, ``Conda``, or ``Spack``. libEnsemble requires the following dependencies, which are typically automatically installed alongside libEnsemble: @@ -13,11 +13,7 @@ automatically installed alongside libEnsemble: * pyyaml_ ``>= v6.0`` * tomli_ ``>= 1.2.1`` -Given libEnsemble's compiled dependencies, the following installation -methods each offer a trade-off between convenience and the ability -to customize builds, including platform-specific optimizations. - -We always recommend installing in a virtual environment from Conda or another source. +We recommend installing in a virtual environment from ``uv``, ``conda`` or another source. Further recommendations for selected HPC systems are given in the :ref:`HPC platform guides`. @@ -53,6 +49,12 @@ Further recommendations for selected HPC systems are given in the CC=mpicc MPICC=mpicc pip install mpi4py --no-binary mpi4py + .. tab-item:: uv + + To install the latest PyPI_ release via uv_:: + + uv pip install libensemble + .. tab-item:: conda Install libEnsemble with Conda_ from the conda-forge channel:: @@ -192,3 +194,4 @@ The following packages may be installed separately to enable additional features .. _spack_libe: https://github.com/Libensemble/spack_libe .. _tomli: https://pypi.org/project/tomli/ .. _tqdm: https://tqdm.github.io/ +.. _uv: https://docs.astral.sh/uv/ diff --git a/docs/libe_module.rst b/docs/libe_module.rst index 6f60d633a..caa957d13 100644 --- a/docs/libe_module.rst +++ b/docs/libe_module.rst @@ -3,19 +3,6 @@ Running an Ensemble =================== -libEnsemble features two approaches to run an ensemble. We recommend the newer ``Ensemble`` class, -but will continue to support ``libE()`` for backward compatibility. - -.. tab-set:: - - .. tab-item:: Ensemble Class - - .. autoclass:: libensemble.ensemble.Ensemble() - :members: - :no-undoc-members: - - .. tab-item:: libE() - - .. automodule:: libensemble.libE - :members: - :no-undoc-members: +.. autoclass:: libensemble.ensemble.Ensemble() + :members: + :no-undoc-members: From 1ff10e1dd5ab70d5740040d34c92066dea55fc43 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 8 Jan 2026 16:29:14 -0600 Subject: [PATCH 04/13] remove sim_specs / gen_specs docs components that describe defining those structures as dictionaries --- docs/data_structures/gen_specs.rst | 113 ++++++++-------------------- docs/data_structures/libE_specs.rst | 9 +-- docs/data_structures/sim_specs.rst | 85 ++++++--------------- 3 files changed, 53 insertions(+), 154 deletions(-) diff --git a/docs/data_structures/gen_specs.rst b/docs/data_structures/gen_specs.rst index 66b12b3cc..b3364e53f 100644 --- a/docs/data_structures/gen_specs.rst +++ b/docs/data_structures/gen_specs.rst @@ -3,89 +3,36 @@ Generator Specs =============== -Used to specify the generator function, its inputs and outputs, and user data. - -Can be constructed and passed to libEnsemble as a Python class or a dictionary. - -.. tab-set:: - - .. tab-item:: class - - .. code-block:: python - :linenos: - - ... - import numpy as np - from libensemble import GenSpecs - from generator import gen_random_sample - - ... - - gen_specs = GenSpecs( - gen_f=gen_random_sample, - outputs=[("x", float, (1,))], - user={ - "lower": np.array([-3]), - "upper": np.array([3]), - "gen_batch_size": 5, - }, - ) - ... - - .. autopydantic_model:: libensemble.specs.GenSpecs - :model-show-json: False - :model-show-config-member: False - :model-show-config-summary: False - :model-show-validator-members: False - :model-show-validator-summary: False - :field-list-validators: False - - .. tab-item:: dict - - .. code-block:: python - :linenos: - - ... - import numpy as np - from generator import gen_random_sample - - ... - - gen_specs = { - "gen_f": gen_random_sample, - "out": [("x", float, (1,))], - "user": { - "lower": np.array([-3]), - "upper": np.array([3]), - "gen_batch_size": 5, - }, - } - - .. seealso:: - - .. _gen-specs-example1: - - - test_uniform_sampling.py_: - the generator function ``uniform_random_sample`` in sampling.py_ will generate 500 random - points uniformly over the 2D domain defined by ``gen_specs["ub"]`` and - ``gen_specs["lb"]``. - - .. literalinclude:: ../../libensemble/tests/functionality_tests/test_uniform_sampling.py - :start-at: gen_specs - :end-before: end_gen_specs_rst_tag - - .. seealso:: - - - test_persistent_aposmm_nlopt.py_ shows an example where ``gen_specs["in"]`` is empty, but - ``gen_specs["persis_in"]`` specifies values to return to the persistent generator. - - - test_persistent_aposmm_with_grad.py_ shows a similar example where an ``H0`` is used to - provide points from a previous run. In this case, ``gen_specs["in"]`` is populated to provide - the generator with data for the initial points. - - - In some cases you might be able to give different (perhaps fewer) fields in ``"persis_in"`` - than ``"in"``; you may not need to give ``x`` for example, as the persistent generator - already has ``x`` for those points. See `more example uses`_ of ``persis_in``. +Used to specify the generator, its inputs and outputs, and user data. + +.. code-block:: python + :linenos: + + ... + import numpy as np + from libensemble import GenSpecs + from generator import gen_random_sample + + ... + + gen_specs = GenSpecs( + gen_f=gen_random_sample, + outputs=[("x", float, (1,))], + user={ + "lower": np.array([-3]), + "upper": np.array([3]), + "gen_batch_size": 5, + }, + ) + ... + +.. autopydantic_model:: libensemble.specs.GenSpecs + :model-show-json: False + :model-show-config-member: False + :model-show-config-summary: False + :model-show-validator-members: False + :model-show-validator-summary: False + :field-list-validators: False .. note:: diff --git a/docs/data_structures/libE_specs.rst b/docs/data_structures/libE_specs.rst index caa7b2eda..bc2df3473 100644 --- a/docs/data_structures/libE_specs.rst +++ b/docs/data_structures/libE_specs.rst @@ -3,18 +3,13 @@ LibE Specs ========== -libEnsemble is primarily customized by setting options within a ``LibeSpecs`` class or dictionary. +libEnsemble is primarily customized by setting options within a ``LibeSpecs`` instance. .. code-block:: python from libensemble.specs import LibeSpecs - specs = LibeSpecs( - gen_on_manager=True, - save_every_k_gens=100, - sim_dirs_make=True, - nworkers=4 - ) + specs = LibeSpecs(gen_on_manager=True, save_every_k_gens=100, sim_dirs_make=True, nworkers=4) .. dropdown:: Settings by Category :open: diff --git a/docs/data_structures/sim_specs.rst b/docs/data_structures/sim_specs.rst index 856ab5a9f..9a023f549 100644 --- a/docs/data_structures/sim_specs.rst +++ b/docs/data_structures/sim_specs.rst @@ -3,75 +3,32 @@ Simulation Specs ================ -Used to specify the simulation function, its inputs and outputs, and user data. +Used to specify the simulation, its inputs and outputs, and user data. -Can be constructed and passed to libEnsemble as a Python class or a dictionary. +.. code-block:: python + :linenos: -.. tab-set:: + ... + from libensemble import SimSpecs + from simulator import sim_find_sine - .. tab-item:: class + ... - .. code-block:: python - :linenos: + sim_specs = SimSpecs( + sim_f=sim_find_sine, + inputs=["x"], + outputs=[("y", float)], + user={"batch": 1234}, + ) + ... - ... - from libensemble import SimSpecs - from simulator import sim_find_sine +.. autopydantic_model:: libensemble.specs.SimSpecs + :model-show-json: False + :model-show-config-member: False + :model-show-config-summary: False + :model-show-validator-members: False + :model-show-validator-summary: False + :field-list-validators: False - ... - sim_specs = SimSpecs( - sim_f=sim_find_sine, - inputs=["x"], - outputs=[("y", float)], - user={"batch": 1234}, - ) - ... - - .. autopydantic_model:: libensemble.specs.SimSpecs - :model-show-json: False - :model-show-config-member: False - :model-show-config-summary: False - :model-show-validator-members: False - :model-show-validator-summary: False - :field-list-validators: False - - .. tab-item:: dict - - .. code-block:: python - :linenos: - - ... - from simulator import six_hump_camel - - ... - - sim_specs = { - "sim_f": six_hump_camel, - "in": ["x"], - "out": [("y", float)], - "user": {"batch": 1234}, - } - ... - - - test_uniform_sampling.py_ has a :class:`sim_specs` that declares - the name of the ``"in"`` field variable, ``"x"`` (as specified by the - corresponding generator ``"out"`` field ``"x"`` from the :ref:`gen_specs - example`). Only the field name is required in - ``sim_specs["in"]``. - - .. literalinclude:: ../../libensemble/tests/functionality_tests/test_uniform_sampling.py - :start-at: sim_specs - :end-before: end_sim_specs_rst_tag - - - run_libe_forces.py_ has a longer :class:`sim_specs` declaration with a number of - user-specific fields. These are given to the corresponding sim_f, which - can be found at forces_simf.py_. - - .. literalinclude:: ../../libensemble/tests/scaling_tests/forces/forces_adv/run_libe_forces.py - :start-at: sim_f - :end-before: end_sim_specs_rst_tag - -.. _forces_simf.py: https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/scaling_tests/forces/forces_simple/forces_simf.py -.. _run_libe_forces.py: https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/scaling_tests/forces/forces_simple/run_libe_forces.py .. _test_uniform_sampling.py: https://github.com/Libensemble/libensemble/blob/develop/libensemble/tests/functionality_tests/test_uniform_sampling.py From cb05162ab41cc37c92f22ace291196ba1b921643 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 9 Jan 2026 09:44:58 -0600 Subject: [PATCH 05/13] bump specifically-mentioned required pydantic version in advanced_installation --- docs/advanced_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced_installation.rst b/docs/advanced_installation.rst index 1ee5f95e7..d002183fa 100644 --- a/docs/advanced_installation.rst +++ b/docs/advanced_installation.rst @@ -9,7 +9,7 @@ automatically installed alongside libEnsemble: * Python_ ``>= 3.10`` * NumPy_ ``>= 1.21`` * psutil_ ``>= 5.9.4`` -* `pydantic`_ ``>= 1.10.12`` +* `pydantic`_ ``>= 2`` * pyyaml_ ``>= v6.0`` * tomli_ ``>= 1.2.1`` From 67fa8e4f4df8ac6b4d2a4d773a7be9da503d83ac Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 9 Jan 2026 11:11:15 -0600 Subject: [PATCH 06/13] initial files and fixes for autodocing the gest-api adhering generators --- docs/conf.py | 2 +- docs/examples/gest_api.rst | 76 +++++++++++++++++++++++++++++ docs/examples/gest_api/aposmm.rst | 7 +++ docs/examples/gest_api/gpcam.rst | 6 +++ docs/examples/gest_api/sampling.rst | 10 ++++ docs/index.rst | 1 + libensemble/gen_classes/aposmm.py | 66 ++++++++++--------------- 7 files changed, 128 insertions(+), 40 deletions(-) create mode 100644 docs/examples/gest_api.rst create mode 100644 docs/examples/gest_api/aposmm.rst create mode 100644 docs/examples/gest_api/gpcam.rst create mode 100644 docs/examples/gest_api/sampling.rst diff --git a/docs/conf.py b/docs/conf.py index 7686b741f..b4a1dd279 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -67,9 +67,9 @@ class AxParameterWarning(Warning): # Ensure it's a real warning subclass # sys.path.insert(0, os.path.abspath('.')) sys.path.append(os.path.abspath("../libensemble")) -##sys.path.append(os.path.abspath('../libensemble')) sys.path.append(os.path.abspath("../libensemble/alloc_funcs")) sys.path.append(os.path.abspath("../libensemble/gen_funcs")) +sys.path.append(os.path.abspath("../libensemble/gen_classes")) sys.path.append(os.path.abspath("../libensemble/sim_funcs")) sys.path.append(os.path.abspath("../libensemble/comms")) sys.path.append(os.path.abspath("../libensemble/utils")) diff --git a/docs/examples/gest_api.rst b/docs/examples/gest_api.rst new file mode 100644 index 000000000..9789a527c --- /dev/null +++ b/docs/examples/gest_api.rst @@ -0,0 +1,76 @@ +(New) Standardized Generators +============================= + +libEnsemble now also supports all generators that implement the gest_api_ interface. + +.. code-block:: python + + from gest_api.vocs import VOCS + from optimas.generators import GridSamplingGenerator + + from libensemble.specs import GenSpecs + + vocs = VOCS( + variables={ + "x0": [-3.0, 2.0], + "x1": [1.0, 5.0], + }, + objectives={"f": "MAXIMIZE"}, + ) + + generator = GridSamplingGenerator(vocs=vocs, n_steps=[7, 15]) + + gen_specs = GenSpecs( + generator=generator, + batch_size=4, + vocs=vocs, + ) + ... + +Here we list many standard-adhering generators included with libEnsemble. + +Sampling +-------- + +.. toctree:: + :maxdepth: 1 + :caption: Sampling + :hidden: + + gest_api/sampling + +- :doc:`Basic sampling` + + Various generators for sampling a space. + +Optimization +------------ + +.. toctree:: + :maxdepth: 1 + :caption: Optimization + :hidden: + + gest_api/aposmm + +- :doc:`APOSMM` + + Asynchronously Parallel Optimization Solver for finding Multiple Minima (paper_). + +Modeling and Approximation +-------------------------- + +.. toctree:: + :maxdepth: 1 + :caption: Modeling and Approximation + :hidden: + + gest_api/gpcam + +- :doc:`gpCAM` + + Gaussian Process-based adaptive sampling using gpcam_. + +.. _gest_api: https://github.com/campa-consortium/gest-api +.. _gpcam: https://gpcam.lbl.gov/ +.. _paper: https://link.springer.com/article/10.1007/s12532-017-0131-4 diff --git a/docs/examples/gest_api/aposmm.rst b/docs/examples/gest_api/aposmm.rst new file mode 100644 index 000000000..bb28c70e9 --- /dev/null +++ b/docs/examples/gest_api/aposmm.rst @@ -0,0 +1,7 @@ +APOSMM +------ + +.. automodule:: gen_classes.aposmm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/examples/gest_api/gpcam.rst b/docs/examples/gest_api/gpcam.rst new file mode 100644 index 000000000..25fdc1c2f --- /dev/null +++ b/docs/examples/gest_api/gpcam.rst @@ -0,0 +1,6 @@ +gpCAM +------ + +.. automodule:: gen_classes.gpCAM + :members: + :undoc-members: diff --git a/docs/examples/gest_api/sampling.rst b/docs/examples/gest_api/sampling.rst new file mode 100644 index 000000000..39f80918b --- /dev/null +++ b/docs/examples/gest_api/sampling.rst @@ -0,0 +1,10 @@ +sampling +-------- + +.. automodule:: gen_classes.sampling + :members: + :undoc-members: + +.. automodule:: gen_classes.external.sampling + :members: + :undoc-members: diff --git a/docs/index.rst b/docs/index.rst index 7182746ab..2a2c40075 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,6 +32,7 @@ :maxdepth: 1 :caption: Examples: + examples/gest_api examples/gen_funcs examples/sim_funcs examples/alloc_funcs diff --git a/libensemble/gen_classes/aposmm.py b/libensemble/gen_classes/aposmm.py index b081a1d07..d697ade3d 100644 --- a/libensemble/gen_classes/aposmm.py +++ b/libensemble/gen_classes/aposmm.py @@ -22,23 +22,23 @@ class APOSMM(PersistentGenInterfacer): `https://doi.org/10.1007/s12532-017-0131-4 `_ - VOCS variables must include both regular and *_on_cube versions. E.g.,: - - ```python - vars_std = { - "var1": [-10.0, 10.0], - "var2": [0.0, 100.0], - "var3": [1.0, 50.0], - "var1_on_cube": [0, 1.0], - "var2_on_cube": [0, 1.0], - "var3_on_cube": [0, 1.0], - } - variables_mapping = { - "x": ["var1", "var2", "var3"], - "x_on_cube": ["var1_on_cube", "var2_on_cube", "var3_on_cube"], - } - gen = APOSMM(vocs, 3, 3, variables_mapping=variables_mapping, ...) - ``` + VOCS variables must include both regular and ``*_on_cube`` versions. E.g.,: + + .. code-block:: python + + vars_std = { + "var1": [-10.0, 10.0], + "var2": [0.0, 100.0], + "var3": [1.0, 50.0], + "var1_on_cube": [0, 1.0], + "var2_on_cube": [0, 1.0], + "var3_on_cube": [0, 1.0], + } + variables_mapping = { + "x": ["var1", "var2", "var3"], + "x_on_cube": ["var1_on_cube", "var2_on_cube", "var3_on_cube"], + } + gen = APOSMM(vocs, 3, 3, variables_mapping=variables_mapping, ...) Getting started --------------- @@ -49,7 +49,8 @@ class APOSMM(PersistentGenInterfacer): parameter. This many evaluated sample points *must* be provided to APOSMM before it will provide any local optimization points. - ```python + .. code-block:: python + # Approach 1: Retrieve sample points via suggest() gen = APOSMM(vocs, max_active_runs=2, initial_sample_size=10) @@ -78,12 +79,14 @@ class APOSMM(PersistentGenInterfacer): points = gen.suggest(10) ... - ``` - *Important Note*: After the initial sample phase, APOSMM cannot accept additional "arbitrary" - sample points that are not associated with local optimization runs. - ```python + .. important:: + After the initial sample phase, APOSMM cannot accept additional "arbitrary" + sample points that are not associated with local optimization runs. + + + .. code-block:: python gen = APOSMM(vocs, max_active_runs=2, initial_sample_size=10) # ask APOSMM for some sample points @@ -99,7 +102,6 @@ class APOSMM(PersistentGenInterfacer): gen.ingest(points_from_aposmm) gen.ingest(another_sample) # THIS CRASHES - ``` Parameters ---------- @@ -113,26 +115,12 @@ class APOSMM(PersistentGenInterfacer): Minimal sample points required before starting optimization. - If `suggest(N)` is called first, APOSMM produces this many random sample points across the domain, + If ``suggest(N)`` is called first, APOSMM produces this many random sample points across the domain, with N <= initial_sample_size. - If `ingest(sample)` is called first, multiple calls like `ingest(sample)` are required until + If ``ingest(sample)`` is called first, multiple calls like ``ingest(sample)`` are required until the total number of points ingested is >= initial_sample_size. - ```python - gen = APOSMM(vocs, max_active_runs=2, initial_sample_size=10) - - # ask APOSMM for some sample points - initial_sample = gen.suggest(10) - for point in initial_sample: - point["f"] = func(point["x"]) - gen.ingest(initial_sample) - - # APOSMM will now provide local-optimization points. - points = gen.suggest(10) - ... - ``` - History: npt.NDArray = [] An optional history of previously evaluated points. From 4039b6e2049e0180014dcb0672b5b5b007f4802c Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 15 Jan 2026 15:36:56 -0600 Subject: [PATCH 07/13] small fixes, plus display some ask/tell aposmm examples in the docs --- docs/examples/gest_api/aposmm.rst | 19 +++++++++++++++++++ libensemble/gen_classes/aposmm.py | 1 + pixi.lock | 4 ++-- pyproject.toml | 1 + 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/examples/gest_api/aposmm.rst b/docs/examples/gest_api/aposmm.rst index bb28c70e9..817abd23a 100644 --- a/docs/examples/gest_api/aposmm.rst +++ b/docs/examples/gest_api/aposmm.rst @@ -5,3 +5,22 @@ APOSMM :members: :undoc-members: :show-inheritance: + + +.. seealso:: + + .. tab-set:: + + .. tab-item:: APOSMM with libEnsemble + + .. literalinclude:: ../../../libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py + :linenos: + :start-at: workflow.libE_specs.gen_on_manager = True + :end-before: # Perform the run + + .. tab-item:: APOSMM standalone + + .. literalinclude:: ../../../libensemble/tests/unit_tests/test_persistent_aposmm.py + :linenos: + :start-at: def test_asktell_ingest_first(): + :end-before: assert persis_info.get("run_order"), "Standalone persistent_aposmm didn't do any localopt runs" diff --git a/libensemble/gen_classes/aposmm.py b/libensemble/gen_classes/aposmm.py index 3882576c7..731696121 100644 --- a/libensemble/gen_classes/aposmm.py +++ b/libensemble/gen_classes/aposmm.py @@ -87,6 +87,7 @@ class APOSMM(PersistentGenInterfacer): .. code-block:: python + gen = APOSMM(vocs, max_active_runs=2, initial_sample_size=10) # ask APOSMM for some sample points diff --git a/pixi.lock b/pixi.lock index 0bcede09f..9b559618c 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:590f013ffb08a53ec3d19f26f536da092df3f35fe098439e10cf1a49ac86f900 -size 1091576 +oid sha256:abd3bccd0e2180907181773aa2b6410fa8a3c72841dbb48cbcc601e97eaa5611 +size 1091601 diff --git a/pyproject.toml b/pyproject.toml index 7165e53e2..09de83246 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -208,6 +208,7 @@ extra = [ "redis>=7.1.0,<8", ] dev = ["wat>=0.7.0,<0.8"] +docs = ["pyenchant==3.2.2", "enchant>=0.0.1,<0.0.2"] # Various config from here onward [tool.black] From fa9c9f6f43d58c6ad3ed0f6e96ec1cad7fb0862e Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 15 Jan 2026 16:00:05 -0600 Subject: [PATCH 08/13] list gpcam example --- docs/examples/gest_api/gpcam.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/examples/gest_api/gpcam.rst b/docs/examples/gest_api/gpcam.rst index 25fdc1c2f..c544f9e63 100644 --- a/docs/examples/gest_api/gpcam.rst +++ b/docs/examples/gest_api/gpcam.rst @@ -4,3 +4,12 @@ gpCAM .. automodule:: gen_classes.gpCAM :members: :undoc-members: + :show-inheritance: + + +.. seealso:: + + .. literalinclude:: ../../../libensemble/tests/regression_tests/test_asktell_gpCAM.py + :linenos: + :start-at: vocs = VOCS(variables={"x0": [-3, 3], "x1": [-2, 2], "x2": [-1, 1], "x3": [-1, 1]}, objectives={"f": "MINIMIZE"}) + :end-before: if is_manager: From c6e56a9a5435b7a5873a4c72ef80130e40e01569 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 16 Jan 2026 10:03:13 -0600 Subject: [PATCH 09/13] monocasing, plus a clarification --- libensemble/gen_classes/aposmm.py | 36 ++++++++++++++--------------- libensemble/gen_classes/sampling.py | 2 ++ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/libensemble/gen_classes/aposmm.py b/libensemble/gen_classes/aposmm.py index 731696121..c843d3fa1 100644 --- a/libensemble/gen_classes/aposmm.py +++ b/libensemble/gen_classes/aposmm.py @@ -44,8 +44,8 @@ class APOSMM(PersistentGenInterfacer): --------------- APOSMM requires a minimal sample before starting optimization. A random sample across the domain - can either be retrieved via a `suggest()` call right after initialization, or the user can ingest - a set of sample points via `ingest()`. The minimal sample size is specified via the `initial_sample_size` + can either be retrieved via a ``suggest()`` call right after initialization, or the user can ingest + a set of sample points via ``ingest()``. The minimal sample size is specified via the ``initial_sample_size`` parameter. This many evaluated sample points *must* be provided to APOSMM before it will provide any local optimization points. @@ -106,58 +106,58 @@ class APOSMM(PersistentGenInterfacer): Parameters ---------- - vocs: VOCS + vocs: ``VOCS`` The VOCS object, adhering to the VOCS interface from the Generator Standard. - max_active_runs: int + max_active_runs: ``int`` Bound on number of runs APOSMM is *concurrently* advancing. - initial_sample_size: int + initial_sample_size: ``int`` Minimal sample points required before starting optimization. If ``suggest(N)`` is called first, APOSMM produces this many random sample points across the domain, - with N <= initial_sample_size. + with ``N <= initial_sample_size``. If ``ingest(sample)`` is called first, multiple calls like ``ingest(sample)`` are required until - the total number of points ingested is >= initial_sample_size. + the total number of points ingested is ``>= initial_sample_size``. - History: npt.NDArray = [] + History: ``npt.NDArray`` = ``[]`` An optional history of previously evaluated points. - sample_points: npt.NDArray = None + sample_points: ``npt.NDArray`` = ``None`` Included for compatibility with the underlying algorithm. 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 = "scipy_Nelder-Mead" (scipy) or "LN_BOBYQA" (nlopt) + localopt_method: ``str`` = "scipy_Nelder-Mead" (scipy) or "LN_BOBYQA" (nlopt) The local optimization method to use. Others being added over time. - mu: float = 1e-8 + mu: ``float`` = ``1e-8`` Distance from the boundary that all localopt starting points must satisfy - nu: float = 1e-8 + nu: ``float`` = ``1e-8`` Distance from identified minima that all starting points must satisfy - rk_const: float = None + 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 + xtol_abs: ``float`` = ``1e-6`` Localopt method's convergence tolerance. - ftol_abs: float = 1e-6 + ftol_abs: ``float`` = ``1e-6`` Localopt method's convergence tolerance. - opt_return_codes: list[int] = [0] + opt_return_codes: ``list[int]`` = ``[0]`` scipy only: List of return codes that determine if a point should be ruled a local minimum. - dist_to_bound_multiple: float = 0.5 + 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. - random_seed: int = 1 + random_seed: ``int`` = ``1`` Seed for the random number generator. """ diff --git a/libensemble/gen_classes/sampling.py b/libensemble/gen_classes/sampling.py index 5e8102c22..4304e12f1 100644 --- a/libensemble/gen_classes/sampling.py +++ b/libensemble/gen_classes/sampling.py @@ -13,6 +13,8 @@ class UniformSample(LibensembleGenerator): """ Samples over the domain specified in the VOCS. + + Implements ``suggest()`` and ``ingest()`` identically to the other generators. """ def __init__(self, VOCS: VOCS, random_seed: int = 1, *args, **kwargs): From c6c79bb6b640cf7825f4c84571561a625f14f7de Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 16 Jan 2026 10:20:44 -0600 Subject: [PATCH 10/13] linenos --- docs/examples/gest_api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/examples/gest_api.rst b/docs/examples/gest_api.rst index 9789a527c..cc7b67276 100644 --- a/docs/examples/gest_api.rst +++ b/docs/examples/gest_api.rst @@ -4,6 +4,8 @@ libEnsemble now also supports all generators that implement the gest_api_ interface. .. code-block:: python + :linenos: + :emphasize-lines: 17 from gest_api.vocs import VOCS from optimas.generators import GridSamplingGenerator From c1b5731c8b54d60576326b7c04a3b3c66cd9070c Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 16 Jan 2026 10:55:14 -0600 Subject: [PATCH 11/13] starting to rewrite the first tutorial for standardized gens --- docs/tutorials/local_sine_tutorial.rst | 55 ++++++++----------- .../tutorials/simple_sine/sine_gen_std.py | 26 +++++++++ .../tests/functionality_tests/sine_gen_std.py | 26 +++++++++ .../test_local_sine_tutorial.py | 17 +++--- 4 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 examples/tutorials/simple_sine/sine_gen_std.py create mode 100644 libensemble/tests/functionality_tests/sine_gen_std.py diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index 050982475..c7799bd76 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -40,45 +40,37 @@ need to write a new allocation function. .. tab-item:: 2. Generator - Let's begin the coding portion of this tutorial by writing our generator function, - or :ref:`gen_f`. + Let's begin the coding portion of this tutorial by writing our generator. - An available libEnsemble worker will call this generator function with the - following parameters: - - * :ref:`InputArray`: A selection of the :ref:`History array` (*H*), - passed to the generator function in case the user wants to generate - new values based on simulation outputs. Since our generator produces random - numbers, it'll be ignored this time. - - * :ref:`persis_info`: Dictionary with worker-specific - information. In our case, this dictionary contains NumPy Random Stream objects - for generating random numbers. - - * :ref:`gen_specs`: Dictionary with user-defined static fields and - parameters. Customizable parameters such as lower and upper bounds and batch - sizes are placed within the ``gen_specs["user"]`` dictionary. - - Later on, we'll populate :class:`gen_specs` and ``persis_info`` when we initialize libEnsemble. + An available libEnsemble worker will call this generator's ``.suggest()`` method to obtain + new values to evaluate. For now, create a new Python file named ``sine_gen.py``. Write the following: - .. literalinclude:: ../../libensemble/tests/functionality_tests/sine_gen.py + .. literalinclude:: ../../libensemble/tests/functionality_tests/sine_gen_std.py :language: python :linenos: - :caption: examples/tutorials/simple_sine/sine_gen.py + :caption: examples/tutorials/simple_sine/sine_gen_std.py + + libEnsemble accepts generators that implement the gest-api_ interface. These generators + accept a ``gest_api.VOCS`` object for configuration, and contain a ``.suggest(num_points)`` + method that returns ``num_points`` points. Points consist of a list of dictionaries + with keys that match the variable names from the ``gest_api.VOCS`` object. + + Our generator's ``suggest()`` method creates ``num_points`` dictionaries. For each key in + the generator's ``self.variables``, it creates a random number uniformly distributed + between the corresponding ``lower`` and ``upper`` bounds of its domain. - Our function creates ``batch_size`` random numbers uniformly distributed - between the ``lower`` and ``upper`` bounds. A random stream - from ``persis_info`` is used to generate these values, which are then placed - into an output NumPy array that matches the dtype from ``gen_specs["out"]``. + Our generator must implement a ``_validate_vocs()`` method to check the validity of the + ``VOCS`` object. Here, we implement a simple check that ensures the + ``VOCS`` object has at least one variable. .. tab-item:: 3. Simulator Next, we'll write our simulator function or :ref:`sim_f`. Simulator - functions perform calculations based on values from the generator function. - The only new parameter here is :ref:`sim_specs`, which - serves a purpose similar to the :class:`gen_specs` dictionary. + functions perform calculations based on values from the generator. + :ref:`sim_specs` is a dictionary containing user-defined fields + and parameters. Create a new Python file named ``sine_sim.py``. Write the following: @@ -88,7 +80,7 @@ need to write a new allocation function. :caption: examples/tutorials/simple_sine/sine_sim.py Our simulator function is called by a worker for every work item produced by - the generator function. This function calculates the sine of the passed value, + the generator. This function calculates the sine of the passed value, and then returns it so the worker can store the result. .. tab-item:: 4. Script @@ -97,8 +89,8 @@ need to write a new allocation function. functions and starts libEnsemble. Create an empty Python file named ``calling.py``. - In this file, we'll start by importing NumPy, libEnsemble's setup classes, - and the generator and simulator functions we just created. + In this file, we'll start by importing NumPy, libEnsemble's setup classes, the generator, + and simulator function. In a class called :ref:`LibeSpecs` we'll specify the number of workers and the manager/worker intercommunication method. @@ -272,6 +264,7 @@ need to write a new allocation function. libEnsemble use-case within our :doc:`Electrostatic Forces tutorial <./executor_forces_tutorial>`. +.. _gest-api: https://github.com/campa-consortium/gest-api .. _Matplotlib: https://matplotlib.org/ .. _MPI: https://en.wikipedia.org/wiki/Message_Passing_Interface .. _MPICH: https://www.mpich.org/ diff --git a/examples/tutorials/simple_sine/sine_gen_std.py b/examples/tutorials/simple_sine/sine_gen_std.py new file mode 100644 index 000000000..43bafbf84 --- /dev/null +++ b/examples/tutorials/simple_sine/sine_gen_std.py @@ -0,0 +1,26 @@ +import numpy as np +from gest_api import Generator + + +class RandomSample(Generator): + """ + This sampler accepts a gest-api VOCS object for configuration and returns random samples. + """ + + def __init__(self, vocs): + self.variables = vocs.variables + self.rng = np.random.default_rng(1) + self._validate_vocs(vocs) + + def _validate_vocs(self, vocs): + if not len(vocs.variables) > 0: + raise ValueError("vocs must have at least one variable") + + def suggest(self, num_points): + output = [] + for _ in range(num_points): + trial = {} + for key in self.variables.keys(): + trial[key] = self.rng.uniform(self.variables[key].domain[0], self.variables[key].domain[1]) + output.append(trial) + return output diff --git a/libensemble/tests/functionality_tests/sine_gen_std.py b/libensemble/tests/functionality_tests/sine_gen_std.py new file mode 100644 index 000000000..43bafbf84 --- /dev/null +++ b/libensemble/tests/functionality_tests/sine_gen_std.py @@ -0,0 +1,26 @@ +import numpy as np +from gest_api import Generator + + +class RandomSample(Generator): + """ + This sampler accepts a gest-api VOCS object for configuration and returns random samples. + """ + + def __init__(self, vocs): + self.variables = vocs.variables + self.rng = np.random.default_rng(1) + self._validate_vocs(vocs) + + def _validate_vocs(self, vocs): + if not len(vocs.variables) > 0: + raise ValueError("vocs must have at least one variable") + + def suggest(self, num_points): + output = [] + for _ in range(num_points): + trial = {} + for key in self.variables.keys(): + trial[key] = self.rng.uniform(self.variables[key].domain[0], self.variables[key].domain[1]) + output.append(trial) + return output diff --git a/libensemble/tests/functionality_tests/test_local_sine_tutorial.py b/libensemble/tests/functionality_tests/test_local_sine_tutorial.py index e05a49c96..f2f3eb58a 100644 --- a/libensemble/tests/functionality_tests/test_local_sine_tutorial.py +++ b/libensemble/tests/functionality_tests/test_local_sine_tutorial.py @@ -1,5 +1,6 @@ import numpy as np -from sine_gen import gen_random_sample +from gest_api.vocs import VOCS +from sine_gen_std import RandomSample from sine_sim import sim_find_sine from libensemble import Ensemble @@ -8,14 +9,14 @@ if __name__ == "__main__": # Python-quirk required on macOS and windows libE_specs = LibeSpecs(nworkers=4, comms="local") + vocs = VOCS(variables={"x": [-3, 3]}, objectives={"y": "EXPLORE"}) # Configure our generator with this object + + generator = RandomSample(vocs) # Instantiate our generator + gen_specs = GenSpecs( - gen_f=gen_random_sample, # Our generator function - out=[("x", float, (1,))], # gen_f output (name, type, size) - user={ - "lower": np.array([-3]), # lower boundary for random sampling - "upper": np.array([3]), # upper boundary for random sampling - "gen_batch_size": 5, # number of x's gen_f generates per call - }, + generator=generator, # Pass our generator and config to libEnsemble + vocs=vocs, + batch_size=4, ) sim_specs = SimSpecs( From b34266be76bfab66ae496e803e0136fce8af48c5 Mon Sep 17 00:00:00 2001 From: jlnav Date: Fri, 16 Jan 2026 14:14:20 -0600 Subject: [PATCH 12/13] remove some redundancy --- docs/tutorials/local_sine_tutorial.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/tutorials/local_sine_tutorial.rst b/docs/tutorials/local_sine_tutorial.rst index c7799bd76..7961aa2b0 100644 --- a/docs/tutorials/local_sine_tutorial.rst +++ b/docs/tutorials/local_sine_tutorial.rst @@ -61,9 +61,8 @@ need to write a new allocation function. the generator's ``self.variables``, it creates a random number uniformly distributed between the corresponding ``lower`` and ``upper`` bounds of its domain. - Our generator must implement a ``_validate_vocs()`` method to check the validity of the - ``VOCS`` object. Here, we implement a simple check that ensures the - ``VOCS`` object has at least one variable. + Our generator must implement a ``_validate_vocs()`` method. Here, we implement a simple + check that ensures the ``VOCS`` object has at least one variable. .. tab-item:: 3. Simulator From b52eff8fd75ca9c4394d42c08ce68676d2ab9cc2 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 21 Jan 2026 11:14:49 -0600 Subject: [PATCH 13/13] link to xopt/optimas generators in docs - also try only doc'ing suggest/ingest/export/finalize for our generators instead of suggest/ingest_numpy --- docs/examples/gest_api.rst | 27 ++++++++++++++++++++++++++- docs/examples/gest_api/aposmm.rst | 4 ++-- docs/examples/gest_api/gpcam.rst | 10 ++++++++-- docs/examples/gest_api/sampling.rst | 8 ++++---- pixi.lock | 4 ++-- 5 files changed, 42 insertions(+), 11 deletions(-) diff --git a/docs/examples/gest_api.rst b/docs/examples/gest_api.rst index cc7b67276..56fee4dcf 100644 --- a/docs/examples/gest_api.rst +++ b/docs/examples/gest_api.rst @@ -1,3 +1,4 @@ +============================= (New) Standardized Generators ============================= @@ -29,7 +30,8 @@ libEnsemble now also supports all generators that implement the gest_api_ interf ) ... -Here we list many standard-adhering generators included with libEnsemble. +Included with libEnsemble +========================= Sampling -------- @@ -73,6 +75,29 @@ Modeling and Approximation Gaussian Process-based adaptive sampling using gpcam_. +Verified Third Party +==================== + +Generators that implement the gest_api_ interface and are verified to work with libEnsemble. + +The standardized interface was developed in partnership with their authors. + +Xopt - https://github.com/xopt-org/Xopt +--------------------------------------- + +`Expected Improvement`_ + +`Nelder Mead`_ + +Optimas - https://github.com/optimas-org/optimas +------------------------------------------------ + +`Grid Sampling`_ + .. _gest_api: https://github.com/campa-consortium/gest-api .. _gpcam: https://gpcam.lbl.gov/ .. _paper: https://link.springer.com/article/10.1007/s12532-017-0131-4 + +.. _Expected Improvement: https://github.com/xopt-org/Xopt/blob/v3.0/xopt/generators/bayesian/expected_improvement.py +.. _Nelder Mead: https://github.com/xopt-org/Xopt/blob/v3.0/xopt/generators/sequential/neldermead.py +.. _Grid Sampling: https://github.com/optimas-org/optimas/blob/main/optimas/generators/grid_sampling.py diff --git a/docs/examples/gest_api/aposmm.rst b/docs/examples/gest_api/aposmm.rst index 817abd23a..a472ced11 100644 --- a/docs/examples/gest_api/aposmm.rst +++ b/docs/examples/gest_api/aposmm.rst @@ -1,8 +1,8 @@ APOSMM ------ -.. automodule:: gen_classes.aposmm - :members: +.. autoclass:: gen_classes.aposmm.APOSMM + :members: suggest, ingest, export, suggest_updates, finalize :undoc-members: :show-inheritance: diff --git a/docs/examples/gest_api/gpcam.rst b/docs/examples/gest_api/gpcam.rst index c544f9e63..8060e2928 100644 --- a/docs/examples/gest_api/gpcam.rst +++ b/docs/examples/gest_api/gpcam.rst @@ -1,8 +1,14 @@ gpCAM ------ -.. automodule:: gen_classes.gpCAM - :members: +.. autoclass:: gen_classes.gpCAM.GP_CAM + :members: suggest, ingest + :undoc-members: + :show-inheritance: + + +.. autoclass:: gen_classes.gpCAM.GP_CAM_Covar + :members: suggest, ingest :undoc-members: :show-inheritance: diff --git a/docs/examples/gest_api/sampling.rst b/docs/examples/gest_api/sampling.rst index 39f80918b..9659dc6e0 100644 --- a/docs/examples/gest_api/sampling.rst +++ b/docs/examples/gest_api/sampling.rst @@ -1,10 +1,10 @@ sampling -------- -.. automodule:: gen_classes.sampling - :members: +.. autoclass:: gen_classes.sampling.UniformSample + :members: suggest, ingest :undoc-members: -.. automodule:: gen_classes.external.sampling - :members: +.. autoclass:: gen_classes.external.sampling.UniformSample + :members: suggest, ingest :undoc-members: diff --git a/pixi.lock b/pixi.lock index 9b559618c..e2b2a3ad8 100644 --- a/pixi.lock +++ b/pixi.lock @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abd3bccd0e2180907181773aa2b6410fa8a3c72841dbb48cbcc601e97eaa5611 -size 1091601 +oid sha256:758bb8abc91caa2b306f539c383bf65ee184a20108d3926b9ba28281f8df32c1 +size 1091594