From 6dd7faf3dc36460729614e39429f01b9118f2c58 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 12:48:41 -0500 Subject: [PATCH 01/21] cleanup additional files after running testsuite --- libensemble/tests/run_tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libensemble/tests/run_tests.py b/libensemble/tests/run_tests.py index 72ef5633e..e566d451f 100755 --- a/libensemble/tests/run_tests.py +++ b/libensemble/tests/run_tests.py @@ -129,6 +129,15 @@ def cleanup(root_dir): "opt_*.txt_flag", "test_executor_forces_tutorial", "test_executor_forces_tutorial_2", + # Coverage output generated by merge_coverage_reports + "coverage.xml", + # Cache files created by Ensemble/calling scripts + ".libe_cache_*.meta.json", + # Artifacts from forces build step + "forces_app", + "scaling_tests/forces/forces_app/forces.x", + # Task output scripts in unit tests + "libe_task_*.sh", ] dirs_to_clean = UNIT_TEST_DIRS + [REG_TEST_SUBDIR, FUNC_TEST_SUBDIR] for dir_path in dirs_to_clean: From 744ed1ff6068774321e298c24cc70bffc32cd0d1 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 13:31:24 -0500 Subject: [PATCH 02/21] introduce improved field-mapping logic from threadrunner Generator to LibensembleGenerator. Fixing various mapping bugs. fix test to catch the fail-case --- .../test_asktell_sampling.py | 5 +- libensemble/tests/unit_tests/test_asktell.py | 22 ++++++++- libensemble/utils/misc.py | 48 +++++++++++-------- libensemble/utils/runners.py | 17 ++++--- 4 files changed, 65 insertions(+), 27 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_asktell_sampling.py b/libensemble/tests/functionality_tests/test_asktell_sampling.py index f2b48547a..908c5eb51 100644 --- a/libensemble/tests/functionality_tests/test_asktell_sampling.py +++ b/libensemble/tests/functionality_tests/test_asktell_sampling.py @@ -88,5 +88,8 @@ def sim_f(In): H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, libE_specs=libE_specs) if is_manager: - print(H[["sim_id", "x", "f"]][:10]) + # Basic sanity checks that we actually saved generated inputs/outputs. assert len(H) >= 201, f"H has length {len(H)}" + assert np.any(np.linalg.norm(H["x"], axis=1) > 0.0), "All saved x values are zero" + assert np.any(H["f"] > 0.0), "All saved f values are zero" + print(H[["sim_id", "x", "f"]][:10]) diff --git a/libensemble/tests/unit_tests/test_asktell.py b/libensemble/tests/unit_tests/test_asktell.py index a7b4979ec..532da1784 100644 --- a/libensemble/tests/unit_tests/test_asktell.py +++ b/libensemble/tests/unit_tests/test_asktell.py @@ -1,6 +1,6 @@ import numpy as np -from libensemble.utils.misc import unmap_numpy_array +from libensemble.utils.misc import map_numpy_array, unmap_numpy_array def _check_conversion(H, npp, mapping={}): @@ -94,6 +94,22 @@ def test_awkward_H(): _check_conversion(H, npp) +def test_map_numpy_array_skips_missing_mapping_sources(): + """Test mapping only uses entries represented in the source array.""" + + dtype = [("x0", float), ("x1", float), ("priority", float)] + H = np.zeros(2, dtype=dtype) + H[0] = (1.1, 2.2, 0.5) + H[1] = (3.3, 4.4, 0.25) + + mapping = {"x": ["x0", "x1"], "f": ["energy"]} + H_mapped = map_numpy_array(H, mapping) + + assert H_mapped.dtype.names == ("x", "priority") + assert np.array_equal(H_mapped["x"], [[1.1, 2.2], [3.3, 4.4]]) + assert np.array_equal(H_mapped["priority"], H["priority"]) + + def test_unmap_numpy_array_basic(): """Test basic unmapping of x and x_on_cube arrays""" @@ -148,6 +164,10 @@ def test_unmap_numpy_array_edge_cases(): H_none = unmap_numpy_array(None, {"x": ["x0", "x1"]}) assert H_none is None + # Mapping entries for absent fields are ignored + H_unmapped = unmap_numpy_array(H, {"missing": ["y"], "x": ["x0", "x1"]}) + assert H_unmapped.dtype.names == ("sim_id", "x0", "x1", "f") + if __name__ == "__main__": # test_awkward_list_dict() diff --git a/libensemble/utils/misc.py b/libensemble/utils/misc.py index 6e3779910..da709f1c0 100644 --- a/libensemble/utils/misc.py +++ b/libensemble/utils/misc.py @@ -187,11 +187,15 @@ def unmap_numpy_array(array: npt.NDArray, mapping: dict = {}) -> npt.NDArray: """ if not mapping or array is None: return array - # Create new dtype with unmapped fields + + active_mapping = {field: mapping[field] for field in array.dtype.names if field in mapping} + if not active_mapping: + return array + new_fields = [] for field in array.dtype.names: - if field in mapping: - for var_name in mapping[field]: + if field in active_mapping: + for var_name in active_mapping[field]: new_fields.append((var_name, array[field].dtype.type)) else: # Preserve the original field structure including per-row shape @@ -199,14 +203,14 @@ def unmap_numpy_array(array: npt.NDArray, mapping: dict = {}) -> npt.NDArray: new_fields.append((field, field_dtype)) unmapped_array = np.zeros(len(array), dtype=new_fields) for field in array.dtype.names: - if field in mapping: + if field in active_mapping: # Unmap array fields if len(array[field].shape) == 1: # Scalar field mapped to single variable - unmapped_array[mapping[field][0]] = array[field] + unmapped_array[active_mapping[field][0]] = array[field] else: # Multi-dimensional field - for i, var_name in enumerate(mapping[field]): + for i, var_name in enumerate(active_mapping[field]): unmapped_array[var_name] = array[field][:, i] else: # Copy non-mapped fields @@ -230,16 +234,25 @@ def map_numpy_array(array: npt.NDArray, mapping: dict = {}) -> npt.NDArray: if not mapping or array is None: return array - # Create new dtype with mapped fields + # Some mappings may apply only on ingest. For example, generator suggestions + # usually contain variables but not objective values. + active_mapping = { + mapped_name: val_list + for mapped_name, val_list in mapping.items() + if all(val in array.dtype.names for val in val_list) + } + if not active_mapping: + return array + new_fields: list[tuple] = [] # Track fields processed by mapping to avoid duplication mapped_source_fields = set() - for key, val_list in mapping.items(): + for val_list in active_mapping.values(): mapped_source_fields.update(val_list) # First add mapped fields from the mapping definition - for mapped_name, val_list in mapping.items(): + for mapped_name, val_list in active_mapping.items(): first_var = val_list[0] # We assume all components have the same type, take from first base_type = array.dtype[first_var] @@ -257,20 +270,17 @@ def map_numpy_array(array: npt.NDArray, mapping: dict = {}) -> npt.NDArray: # remove duplicates from new_fields new_fields = list(dict.fromkeys(new_fields)) - # Create the new array mapped_array = np.zeros(len(array), dtype=new_fields) - # Fill the new array for field in mapped_array.dtype.names: - # Mapped field: stack the source columns - val_list = mapping[field] - if len(val_list) == 1: - mapped_array[field] = array[val_list[0]] + if field in active_mapping: + val_list = active_mapping[field] + if len(val_list) == 1: + mapped_array[field] = array[val_list[0]] + else: + mapped_array[field] = np.stack([array[val] for val in val_list], axis=1) else: - # Stack columns horizontally for each row - # We need to extract each column, then stack them along axis 1 - cols = [array[val] for val in val_list] - mapped_array[field] = np.stack(cols, axis=1) + mapped_array[field] = array[field] return mapped_array diff --git a/libensemble/utils/runners.py b/libensemble/utils/runners.py index b6ac823e1..72f8d1d4f 100644 --- a/libensemble/utils/runners.py +++ b/libensemble/utils/runners.py @@ -174,8 +174,7 @@ def _create_initial_sample(self, sample_method, num_points): } if sample_method not in samplers: raise ValueError( - f"Unknown initial_sample_method: {sample_method!r}. " - f"Supported: {list(samplers.keys())}" + f"Unknown initial_sample_method: {sample_method!r}. " f"Supported: {list(samplers.keys())}" ) sampler = samplers[sample_method](vocs=self.specs.get("vocs")) else: @@ -234,13 +233,19 @@ def _result(self, calc_in: npt.NDArray, persis_info: dict, libE_info: dict) -> ( class LibensembleGenRunner(StandardGenRunner): def _get_initial_suggest(self, libE_info) -> npt.NDArray: - """Get initial batch from generator based on generator type""" + """Get initial batch from a LibensembleGenerator. + + LibensembleGenerator.suggest_numpy emits VOCS-field-named structured arrays + (e.g. x0/x1, energy). The manager-side history expects mapped fields (x, f) + unless the user explicitly requested otherwise. + """ initial_batch = self.specs.get("initial_batch_size") or self.specs.get("batch_size") or libE_info["batch_size"] H_out = self.gen.suggest_numpy(initial_batch) - return H_out + return map_numpy_array(H_out, mapping=getattr(self.gen, "variables_mapping", {})) def _get_points_updates(self, batch_size: int) -> (npt.NDArray, list): numpy_out = self.gen.suggest_numpy(batch_size) + numpy_out = map_numpy_array(numpy_out, mapping=getattr(self.gen, "variables_mapping", {})) if callable(getattr(self.gen, "suggest_updates", None)): updates = self.gen.suggest_updates() else: @@ -248,10 +253,10 @@ def _get_points_updates(self, batch_size: int) -> (npt.NDArray, list): return numpy_out, updates def _convert_ingest(self, x: npt.NDArray) -> list: - self.gen.ingest_numpy(x) + self.gen.ingest_numpy(unmap_numpy_array(x, mapping=getattr(self.gen, "variables_mapping", {}))) def _convert_initial_ingest(self, x: npt.NDArray) -> list: - self.gen.ingest_numpy(x) + self.gen.ingest_numpy(unmap_numpy_array(x, mapping=getattr(self.gen, "variables_mapping", {}))) class LibensembleGenThreadRunner(StandardGenRunner): From ff9120f19c06a25703f26b47ee9a041cee494f39 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 15:03:52 -0500 Subject: [PATCH 03/21] bump sim_max and max_active_runs --- .../test_asktell_aposmm_nlopt.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py b/libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py index 390582143..3dfd639de 100644 --- a/libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py +++ b/libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py @@ -49,7 +49,6 @@ def six_hump_camel_func(x): # Main block is necessary only when using local comms with spawn start method (default on macOS and Windows). if __name__ == "__main__": - workflow = Ensemble(parse_args=True) if workflow.is_manager: @@ -58,14 +57,23 @@ def six_hump_camel_func(x): n = 2 vocs = VOCS( - variables={"core": [-3, 3], "edge": [-2, 2], "core_on_cube": [0, 1], "edge_on_cube": [0, 1]}, + variables={ + "core": [-3, 3], + "edge": [-2, 2], + "core_on_cube": [0, 1], + "edge_on_cube": [0, 1], + }, objectives={"energy": "MINIMIZE"}, ) aposmm = APOSMM( vocs, - max_active_runs=max(1, workflow.nworkers - 1), - variables_mapping={"x": ["core", "edge"], "x_on_cube": ["core_on_cube", "edge_on_cube"], "f": ["energy"]}, + max_active_runs=6, + variables_mapping={ + "x": ["core", "edge"], + "x_on_cube": ["core_on_cube", "edge_on_cube"], + "f": ["energy"], + }, initial_sample_size=100, sample_points=np.round(minima, 1), localopt_method="LN_BOBYQA", @@ -82,7 +90,7 @@ def six_hump_camel_func(x): ) workflow.sim_specs = SimSpecs(simulator=six_hump_camel_func, vocs=vocs) - workflow.exit_criteria = ExitCriteria(sim_max=2000, wallclock_max=600) + workflow.exit_criteria = ExitCriteria(sim_max=3000, wallclock_max=600) # Perform the run H, _, _ = workflow.run() From aa9784e7b40783e5936bc33e2205ed7b58745c95 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 15:41:55 -0500 Subject: [PATCH 04/21] trying to reduce time for test_cancel_in_alloc --- .../tests/functionality_tests/test_cancel_in_alloc.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_cancel_in_alloc.py b/libensemble/tests/functionality_tests/test_cancel_in_alloc.py index f0bcead55..2c272e8cc 100644 --- a/libensemble/tests/functionality_tests/test_cancel_in_alloc.py +++ b/libensemble/tests/functionality_tests/test_cancel_in_alloc.py @@ -36,7 +36,7 @@ "sim_f": sim_f, "in": ["x"], "out": [("f", float)], - "user": {"uniform_random_pause_ub": 10}, + "user": {"uniform_random_pause_ub": 5}, } gen_specs = { @@ -62,7 +62,13 @@ exit_criteria = {"sim_max": 10, "wallclock_max": 300} # Perform the run - H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, libE_specs=libE_specs, alloc_specs=alloc_specs) + H, persis_info, flag = libE( + sim_specs, + gen_specs, + exit_criteria, + libE_specs=libE_specs, + alloc_specs=alloc_specs, + ) if is_manager: test = np.any(H["cancel_requested"]) and np.any(H["kill_sent"]) From 4fa8582b5468734e7c16a99edb5834c5982eb2f8 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 15:44:33 -0500 Subject: [PATCH 05/21] decrement gen_max in test_asktell_sampling.py - since this test takes 30 seconds on ci? --- .../tests/functionality_tests/test_asktell_sampling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_asktell_sampling.py b/libensemble/tests/functionality_tests/test_asktell_sampling.py index 908c5eb51..95a6ebdef 100644 --- a/libensemble/tests/functionality_tests/test_asktell_sampling.py +++ b/libensemble/tests/functionality_tests/test_asktell_sampling.py @@ -56,7 +56,7 @@ def sim_f(In): vocs = VOCS(variables=variables, objectives=objectives) - exit_criteria = {"gen_max": 201} + exit_criteria = {"gen_max": 101} for test in range(3): if test == 0: @@ -89,7 +89,7 @@ def sim_f(In): if is_manager: # Basic sanity checks that we actually saved generated inputs/outputs. - assert len(H) >= 201, f"H has length {len(H)}" + assert len(H) >= 101, f"H has length {len(H)}" assert np.any(np.linalg.norm(H["x"], axis=1) > 0.0), "All saved x values are zero" assert np.any(H["f"] > 0.0), "All saved f values are zero" print(H[["sim_id", "x", "f"]][:10]) From ca5177518a80f5f46675196f0bc367944d90bb5f Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 15:54:23 -0500 Subject: [PATCH 06/21] using dry_run and smaller sim_max to decrease test_stats_output runtime --- .../tests/functionality_tests/test_stats_output.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_stats_output.py b/libensemble/tests/functionality_tests/test_stats_output.py index 015bd06f1..0f9bf7e1a 100644 --- a/libensemble/tests/functionality_tests/test_stats_output.py +++ b/libensemble/tests/functionality_tests/test_stats_output.py @@ -58,7 +58,10 @@ "sim_f": sim_f, "in": ["x"], "out": [("f", float)], - "user": {"app": "helloworld"}, # helloworld or six_hump_camel + "user": { + "app": "helloworld", + "dry_run": True, + }, # dry_run avoids real MPI launches; stats format still exercised } gen_specs = { @@ -86,13 +89,13 @@ # This can improve scheduling when tasks may run across multiple nodes libE_specs["scheduler_opts"] = {"match_slots": False} - exit_criteria = {"sim_max": 40, "wallclock_max": 300} + exit_criteria = {"sim_max": 12, "wallclock_max": 60} iterations = 2 # Note that libE_stats.txt output will be appended across libE calls. for prob_id in range(iterations): - sim_specs["user"]["app"] = "six_hump_camel" + sim_specs["user"]["app"] = "helloworld" libE_specs["ensemble_dir_path"] = ( "./ensemble_test_stats" + str(nworkers) + "_" + libE_specs.get("comms") + "_" + str(prob_id) From aac8054c61d7c7ecf6231cc604b90e6fda04a05d Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 15:58:58 -0500 Subject: [PATCH 07/21] cutting gen_max for test_asktell_sampling --- .../tests/functionality_tests/test_asktell_sampling.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_asktell_sampling.py b/libensemble/tests/functionality_tests/test_asktell_sampling.py index 95a6ebdef..49d86f317 100644 --- a/libensemble/tests/functionality_tests/test_asktell_sampling.py +++ b/libensemble/tests/functionality_tests/test_asktell_sampling.py @@ -43,8 +43,8 @@ def sim_f(In): gen_specs = { "persis_in": ["x", "f", "sim_id"], "out": [("x", float, (2,))], - "initial_batch_size": 20, - "batch_size": 10, + "initial_batch_size": 2, + "batch_size": 1, "user": { "lb": np.array([-3, -2]), "ub": np.array([3, 2]), @@ -56,7 +56,7 @@ def sim_f(In): vocs = VOCS(variables=variables, objectives=objectives) - exit_criteria = {"gen_max": 101} + exit_criteria = {"gen_max": 11} for test in range(3): if test == 0: @@ -89,7 +89,7 @@ def sim_f(In): if is_manager: # Basic sanity checks that we actually saved generated inputs/outputs. - assert len(H) >= 101, f"H has length {len(H)}" + assert len(H) >= 11, f"H has length {len(H)}" assert np.any(np.linalg.norm(H["x"], axis=1) > 0.0), "All saved x values are zero" assert np.any(H["f"] > 0.0), "All saved f values are zero" print(H[["sim_id", "x", "f"]][:10]) From 20ccb03c9b5c5929bc75ae30498a6bc52ef175f5 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 16:03:31 -0500 Subject: [PATCH 08/21] cut sim_max for test_persistent_sampling_CUDA_variable_resources --- .../test_persistent_sampling_CUDA_variable_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libensemble/tests/functionality_tests/test_persistent_sampling_CUDA_variable_resources.py b/libensemble/tests/functionality_tests/test_persistent_sampling_CUDA_variable_resources.py index 5d8fce55a..584049de6 100644 --- a/libensemble/tests/functionality_tests/test_persistent_sampling_CUDA_variable_resources.py +++ b/libensemble/tests/functionality_tests/test_persistent_sampling_CUDA_variable_resources.py @@ -70,7 +70,7 @@ } libE_specs["scheduler_opts"] = {"match_slots": True} - exit_criteria = {"sim_max": 40, "wallclock_max": 300} + exit_criteria = {"sim_max": 10, "wallclock_max": 300} # Perform the run From 6d6a0ced976fcf0a079d0b808496ca48049e8fac Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 16:04:56 -0500 Subject: [PATCH 09/21] cut test_persistent_uniform_sampling_async gen_max --- .../test_persistent_uniform_sampling_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libensemble/tests/functionality_tests/test_persistent_uniform_sampling_async.py b/libensemble/tests/functionality_tests/test_persistent_uniform_sampling_async.py index 9dde0f620..fc9632a58 100644 --- a/libensemble/tests/functionality_tests/test_persistent_uniform_sampling_async.py +++ b/libensemble/tests/functionality_tests/test_persistent_uniform_sampling_async.py @@ -56,7 +56,7 @@ }, } - exit_criteria = {"gen_max": 100, "wallclock_max": 300} + exit_criteria = {"gen_max": 10, "wallclock_max": 300} # Perform the run H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, libE_specs=libE_specs) From 57493c812edbba3fba4c9c740517be90ddeb6a59 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 16:09:52 -0500 Subject: [PATCH 10/21] cut sim_max for test_GPU_variable_resources --- .../tests/regression_tests/test_GPU_variable_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libensemble/tests/regression_tests/test_GPU_variable_resources.py b/libensemble/tests/regression_tests/test_GPU_variable_resources.py index 5fb558102..c8455b459 100644 --- a/libensemble/tests/regression_tests/test_GPU_variable_resources.py +++ b/libensemble/tests/regression_tests/test_GPU_variable_resources.py @@ -78,7 +78,7 @@ # Run with random num_procs/num_gpus for each simulation gpu_test.persis_info = {} - gpu_test.exit_criteria = ExitCriteria(sim_max=20) + gpu_test.exit_criteria = ExitCriteria(sim_max=10) gpu_test.run() if gpu_test.is_manager: From f015c696c317551e89b76e43a953262c4a995619 Mon Sep 17 00:00:00 2001 From: jlnav Date: Wed, 27 May 2026 16:12:45 -0500 Subject: [PATCH 11/21] cut sim_max for test_GPU_variable_resources_multi_task --- .../regression_tests/test_GPU_variable_resources_multi_task.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libensemble/tests/regression_tests/test_GPU_variable_resources_multi_task.py b/libensemble/tests/regression_tests/test_GPU_variable_resources_multi_task.py index 3fa3f4aef..5564e7f96 100644 --- a/libensemble/tests/regression_tests/test_GPU_variable_resources_multi_task.py +++ b/libensemble/tests/regression_tests/test_GPU_variable_resources_multi_task.py @@ -87,7 +87,7 @@ }, ) - gpu_test.exit_criteria = ExitCriteria(sim_max=40, wallclock_max=300) + gpu_test.exit_criteria = ExitCriteria(sim_max=10, wallclock_max=300) if gpu_test.ready(): gpu_test.run() From eebf58bdcec3ee2ab5f106de754cc086f75af3bc Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 09:17:15 -0500 Subject: [PATCH 12/21] also bump sim_max for test_persistent_aposmm_nlopt --- .../regression_tests/test_persistent_aposmm_nlopt.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libensemble/tests/regression_tests/test_persistent_aposmm_nlopt.py b/libensemble/tests/regression_tests/test_persistent_aposmm_nlopt.py index b92850143..28da42d53 100644 --- a/libensemble/tests/regression_tests/test_persistent_aposmm_nlopt.py +++ b/libensemble/tests/regression_tests/test_persistent_aposmm_nlopt.py @@ -81,10 +81,16 @@ alloc_specs = {"alloc_f": alloc_f} - exit_criteria = {"sim_max": 2000} + exit_criteria = {"sim_max": 3000} # Perform the run - H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, alloc_specs=alloc_specs, libE_specs=libE_specs) + H, persis_info, flag = libE( + sim_specs, + gen_specs, + exit_criteria, + alloc_specs=alloc_specs, + libE_specs=libE_specs, + ) if is_manager: print("[Manager]:", H[np.where(H["local_min"])]["x"]) From cdc40cb285fea87cca09ab2833bef78ae1e2eb07 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 09:23:07 -0500 Subject: [PATCH 13/21] num test case adjusts for various tests --- .../tests/functionality_tests/test_new_field.py | 10 ++++++++-- .../test_persistent_sim_uniform_sampling.py | 2 +- .../test_persistent_uniform_sampling.py | 2 +- .../test_uniform_sampling_with_variable_resources.py | 9 +++++++-- .../regression_tests/test_persistent_surmise_calib.py | 2 +- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_new_field.py b/libensemble/tests/functionality_tests/test_new_field.py index a96ec6fc3..158edd60b 100644 --- a/libensemble/tests/functionality_tests/test_new_field.py +++ b/libensemble/tests/functionality_tests/test_new_field.py @@ -11,7 +11,7 @@ # Do not change these lines - they are parsed by run-tests.sh # TESTSUITE_COMMS: mpi local -# TESTSUITE_NPROCS: 2 4 +# TESTSUITE_NPROCS: 4 import numpy as np @@ -55,7 +55,13 @@ def sim_f(In): exit_criteria = {"gen_max": 501} - H, persis_info, flag = libE(sim_specs, gen_specs, exit_criteria, alloc_specs=alloc_specs, libE_specs=libE_specs) + H, persis_info, flag = libE( + sim_specs, + gen_specs, + exit_criteria, + alloc_specs=alloc_specs, + libE_specs=libE_specs, + ) if is_manager: assert len(H) >= 501 diff --git a/libensemble/tests/functionality_tests/test_persistent_sim_uniform_sampling.py b/libensemble/tests/functionality_tests/test_persistent_sim_uniform_sampling.py index 8649614b9..7314f42fc 100644 --- a/libensemble/tests/functionality_tests/test_persistent_sim_uniform_sampling.py +++ b/libensemble/tests/functionality_tests/test_persistent_sim_uniform_sampling.py @@ -14,7 +14,7 @@ # Do not change these lines - they are parsed by run-tests.sh # TESTSUITE_COMMS: mpi local tcp -# TESTSUITE_NPROCS: 3 4 +# TESTSUITE_NPROCS: 4 # TESTSUITE_OS_SKIP: WIN import sys diff --git a/libensemble/tests/functionality_tests/test_persistent_uniform_sampling.py b/libensemble/tests/functionality_tests/test_persistent_uniform_sampling.py index f5f591da9..e9a4c75b2 100644 --- a/libensemble/tests/functionality_tests/test_persistent_uniform_sampling.py +++ b/libensemble/tests/functionality_tests/test_persistent_uniform_sampling.py @@ -14,7 +14,7 @@ # Do not change these lines - they are parsed by run-tests.sh # TESTSUITE_COMMS: mpi local -# TESTSUITE_NPROCS: 3 4 +# TESTSUITE_NPROCS: 4 import sys diff --git a/libensemble/tests/functionality_tests/test_uniform_sampling_with_variable_resources.py b/libensemble/tests/functionality_tests/test_uniform_sampling_with_variable_resources.py index 1fb9d0bc8..cf6eda18a 100644 --- a/libensemble/tests/functionality_tests/test_uniform_sampling_with_variable_resources.py +++ b/libensemble/tests/functionality_tests/test_uniform_sampling_with_variable_resources.py @@ -13,7 +13,7 @@ # Do not change these lines - they are parsed by run-tests.sh # TESTSUITE_COMMS: mpi local -# TESTSUITE_NPROCS: 2 4 +# TESTSUITE_NPROCS: 4 # TESTSUITE_EXTRA: true import sys @@ -114,7 +114,12 @@ # Perform the run H, persis_info, flag = libE( - sim_specs, gen_specs, exit_criteria, persis_info, libE_specs=libE_specs, alloc_specs=alloc_specs + sim_specs, + gen_specs, + exit_criteria, + persis_info, + libE_specs=libE_specs, + alloc_specs=alloc_specs, ) if is_manager: diff --git a/libensemble/tests/regression_tests/test_persistent_surmise_calib.py b/libensemble/tests/regression_tests/test_persistent_surmise_calib.py index 67909440c..3820bfdec 100644 --- a/libensemble/tests/regression_tests/test_persistent_surmise_calib.py +++ b/libensemble/tests/regression_tests/test_persistent_surmise_calib.py @@ -23,7 +23,7 @@ # Do not change these lines - they are parsed by run-tests.sh # TESTSUITE_COMMS: mpi local tcp -# TESTSUITE_NPROCS: 3 4 +# TESTSUITE_NPROCS: 4 # TESTSUITE_EXTRA: true # TESTSUITE_OS_SKIP: OSX From 19f4bea61ac1effeb2208b44dc7b75fdcd1d6193 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 09:57:40 -0500 Subject: [PATCH 14/21] tweaking cancel_in_alloc sim_max --- .../tests/functionality_tests/test_cancel_in_alloc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_cancel_in_alloc.py b/libensemble/tests/functionality_tests/test_cancel_in_alloc.py index 2c272e8cc..511ca2649 100644 --- a/libensemble/tests/functionality_tests/test_cancel_in_alloc.py +++ b/libensemble/tests/functionality_tests/test_cancel_in_alloc.py @@ -36,14 +36,14 @@ "sim_f": sim_f, "in": ["x"], "out": [("f", float)], - "user": {"uniform_random_pause_ub": 5}, + "user": {"uniform_random_pause_ub": 10}, } gen_specs = { "gen_f": gen_f, "in": ["sim_id"], "out": [("x", float, (2,))], - "batch_size": 5, + "batch_size": nworkers, "num_active_gens": 1, "user": { "lb": np.array([-3, -2]), @@ -59,7 +59,7 @@ }, } - exit_criteria = {"sim_max": 10, "wallclock_max": 300} + exit_criteria = {"sim_max": nworkers, "wallclock_max": 300} # Perform the run H, persis_info, flag = libE( From 23bee3313ff9e93acdc987cb5a251ce62f63caac Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 10:05:15 -0500 Subject: [PATCH 15/21] wowee --- libensemble/tests/functionality_tests/test_cancel_in_alloc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_cancel_in_alloc.py b/libensemble/tests/functionality_tests/test_cancel_in_alloc.py index 511ca2649..886944df0 100644 --- a/libensemble/tests/functionality_tests/test_cancel_in_alloc.py +++ b/libensemble/tests/functionality_tests/test_cancel_in_alloc.py @@ -43,7 +43,7 @@ "gen_f": gen_f, "in": ["sim_id"], "out": [("x", float, (2,))], - "batch_size": nworkers, + "batch_size": nworkers * 2, "num_active_gens": 1, "user": { "lb": np.array([-3, -2]), @@ -59,7 +59,7 @@ }, } - exit_criteria = {"sim_max": nworkers, "wallclock_max": 300} + exit_criteria = {"sim_max": nworkers * 2, "wallclock_max": 300} # Perform the run H, persis_info, flag = libE( From 4d9682c3ebc4aca04cff09be49797801015101c1 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 10:33:52 -0500 Subject: [PATCH 16/21] guarantee that cancels due to long runtimes actually happen in cancel_in_alloc --- .../functionality_tests/test_cancel_in_alloc.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_cancel_in_alloc.py b/libensemble/tests/functionality_tests/test_cancel_in_alloc.py index 886944df0..0098ba93f 100644 --- a/libensemble/tests/functionality_tests/test_cancel_in_alloc.py +++ b/libensemble/tests/functionality_tests/test_cancel_in_alloc.py @@ -1,8 +1,8 @@ """ Runs libEnsemble in order to test the ability of an allocation function to cancel long-running simulations. In this case, the simulation has a run-time -in seconds that is drawn uniformly from [0,10] and any time the allocation -function is called and a sim_id has been evaluated for more than 5 seconds, +in seconds that is drawn uniformly from [0,60] and any time the allocation +function is called and a sim_id has been evaluated for more than 0.1 seconds, it is cancelled. Execute via one of the following commands (e.g. 3 workers): @@ -36,14 +36,14 @@ "sim_f": sim_f, "in": ["x"], "out": [("f", float)], - "user": {"uniform_random_pause_ub": 10}, + "user": {"uniform_random_pause_ub": 10}, # long sleep ensures sims are still running when cancel fires } gen_specs = { "gen_f": gen_f, "in": ["sim_id"], "out": [("x", float, (2,))], - "batch_size": nworkers * 2, + "batch_size": nworkers, "num_active_gens": 1, "user": { "lb": np.array([-3, -2]), @@ -54,12 +54,12 @@ alloc_specs = { "alloc_f": give_sim_work_first, "user": { - "cancel_sims_time": 3, + "cancel_sims_time": 0.1, # fires on first alloc call after dispatch, before any sim can return "batch_mode": False, }, } - exit_criteria = {"sim_max": nworkers * 2, "wallclock_max": 300} + exit_criteria = {"sim_max": nworkers * 2, "wallclock_max": 30} # Perform the run H, persis_info, flag = libE( From eb1118680e27ee8a55266ac9e3c1ef2afeb64091 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 12:03:42 -0500 Subject: [PATCH 17/21] bump initial sample size --- libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py b/libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py index 3dfd639de..2a68d6e72 100644 --- a/libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py +++ b/libensemble/tests/regression_tests/test_asktell_aposmm_nlopt.py @@ -74,7 +74,7 @@ def six_hump_camel_func(x): "x_on_cube": ["core_on_cube", "edge_on_cube"], "f": ["energy"], }, - initial_sample_size=100, + initial_sample_size=200, sample_points=np.round(minima, 1), localopt_method="LN_BOBYQA", rk_const=0.5 * ((gamma(1 + (n / 2)) * 5) ** (1 / n)) / sqrt(pi), From b48c36f3b8d99c7a027fab92d92ae8021385543b Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 12:05:36 -0500 Subject: [PATCH 18/21] bump pixi versions --- .github/workflows/basic.yml | 2 +- .github/workflows/extra.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index a5ca0ebfd..bd353bd30 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -48,7 +48,7 @@ jobs: - uses: prefix-dev/setup-pixi@v0.9.6 with: - pixi-version: v0.68.1 + pixi-version: v0.69.0 frozen: true environments: ${{ matrix.python-version }} activate-environment: ${{ matrix.python-version }} diff --git a/.github/workflows/extra.yml b/.github/workflows/extra.yml index 168223bbd..0f4e1fb2e 100644 --- a/.github/workflows/extra.yml +++ b/.github/workflows/extra.yml @@ -50,7 +50,7 @@ jobs: - uses: prefix-dev/setup-pixi@v0.9.6 with: - pixi-version: v0.68.1 + pixi-version: v0.69.0 cache: true frozen: true environments: ${{ matrix.python-version }} From 22f0f22bca46d15a8af86d33c0534797f64ef528 Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 12:38:54 -0500 Subject: [PATCH 19/21] cut the default dist_to_bound_multiple from 0.5 to 0.05 --- libensemble/gen_classes/aposmm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libensemble/gen_classes/aposmm.py b/libensemble/gen_classes/aposmm.py index de7d7a9c1..0ecad8968 100644 --- a/libensemble/gen_classes/aposmm.py +++ b/libensemble/gen_classes/aposmm.py @@ -183,7 +183,7 @@ def __init__( opt_return_codes: list[int] = [0], mu: float = 1e-8, nu: float = 1e-8, - dist_to_bound_multiple: float = 0.5, + dist_to_bound_multiple: float = 0.05, random_seed: int = 1, **kwargs, ) -> None: From bfcfc17d7c7f3ddecc49001bfaa46ed0e4113bce Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 12:46:54 -0500 Subject: [PATCH 20/21] lets consistent run this with only the larger number of processes --- .../test_uniform_sampling_cancel.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/libensemble/tests/functionality_tests/test_uniform_sampling_cancel.py b/libensemble/tests/functionality_tests/test_uniform_sampling_cancel.py index 67aa6973b..a3023da7e 100644 --- a/libensemble/tests/functionality_tests/test_uniform_sampling_cancel.py +++ b/libensemble/tests/functionality_tests/test_uniform_sampling_cancel.py @@ -14,7 +14,7 @@ # Do not change these lines - they are parsed by run-tests.sh # TESTSUITE_COMMS: mpi local -# TESTSUITE_NPROCS: 2 4 +# TESTSUITE_NPROCS: 4 import gc @@ -41,7 +41,15 @@ def create_H0(persis_info, gen_specs, sim_max): n = len(lb) b = sim_max - H0 = np.zeros(b, dtype=[("x", float, 2), ("sim_id", int), ("sim_started", bool), ("cancel_requested", bool)]) + H0 = np.zeros( + b, + dtype=[ + ("x", float, 2), + ("sim_id", int), + ("sim_started", bool), + ("cancel_requested", bool), + ], + ) rng = get_rng(gen_specs, {}) H0["x"] = rng.uniform(lb, ub, (b, n)) H0["sim_id"] = range(b) @@ -144,7 +152,13 @@ def create_H0(persis_info, gen_specs, sim_max): # Perform the run - do not overwrite persis_info H, persis_out, flag = libE( - sim_specs, gen_specs, exit_criteria, persis_info, alloc_specs=alloc_specs, libE_specs=libE_specs, H0=H0 + sim_specs, + gen_specs, + exit_criteria, + persis_info, + alloc_specs=alloc_specs, + libE_specs=libE_specs, + H0=H0, ) if is_manager: From 4e1ab9ed25755631cf8baca4b12ed52136abdabe Mon Sep 17 00:00:00 2001 From: jlnav Date: Thu, 28 May 2026 12:56:46 -0500 Subject: [PATCH 21/21] hugely bump tol, since we're just testing allocs --- .../tests/functionality_tests/test_uniform_sampling_cancel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libensemble/tests/functionality_tests/test_uniform_sampling_cancel.py b/libensemble/tests/functionality_tests/test_uniform_sampling_cancel.py index a3023da7e..98e2b7814 100644 --- a/libensemble/tests/functionality_tests/test_uniform_sampling_cancel.py +++ b/libensemble/tests/functionality_tests/test_uniform_sampling_cancel.py @@ -165,7 +165,7 @@ def create_H0(persis_info, gen_specs, sim_max): assert flag == 0 assert np.all(H["cancel_requested"][::10]), "Some values should be cancelled but are not" assert np.all(~H["sim_started"][::10]), "Some values are given that should not have been" - tol = 0.1 + tol = 0.5 for m in minima: assert np.min(np.sum((H["x"] - m) ** 2, 1)) < tol