From 0b6f84e6c4fcc766cc9bf87f1ca8c81ac7d479e6 Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 20 Nov 2025 12:30:22 +0100 Subject: [PATCH 1/3] initial commit --- docs/source/migration_guide.md | 102 +++++++++++++++++++++++++ docs/source/vensim-tips-and-tricks.rst | 5 +- 2 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 docs/source/migration_guide.md diff --git a/docs/source/migration_guide.md b/docs/source/migration_guide.md new file mode 100644 index 000000000..28a0dd665 --- /dev/null +++ b/docs/source/migration_guide.md @@ -0,0 +1,102 @@ + + +# EMA Workbench Migration Guide + +## Workbench 3.0 + +Version 3.0 is an extensive update of the workbench, drawing in developments in the python +ecosystem over the last decade. A large part of the API has remained the same, but there +are a number of backward incompatible changes. + +### naming of parameters +Perhaps the biggest change is that parameter names now need to be valid python identifiers. +That is, you cannot simply use a number or have spaces in the names of your parameters. For +those connecting to models in python, this change is not a big deal because their +parameter names were already valid python identifiers. + +For Vensim users, the requirement that parameter names need to be valid python identifiers +might seem annoying, because the default behavior of the workbench is to make the parameter +name to a vensim variable. So, you have underscored on the python side, you'll need underscores +in vensim as well. To make life simpler for vensim users, therefore, by default, `VensimModel` +will process parameter names and replace underscores with spaces. + +```python +# old +model = VensimModel( + "flu", wd=r"./models/flu", model_file=r"FLUvensimV1basecase.vpm" +) +model.uncertainties = [ + RealParameter("additional seasonal immune population fraction R1", 0, 0.5), + RealParameter("additional seasonal immune population fraction R2", 0, 0.5,), + RealParameter("fatality ratio region 1", 0.0001, 0.1,), + RealParameter("fatality rate region 2", 0.0001, 0.1,)] + +# new +model = VensimModel( + "flu", wd=r"./models/flu", model_file=r"FLUvensimV1basecase.vpm" +) +model.uncertainties = [ + RealParameter("additional_seasonal_immune_population_fraction_R1", 0, 0.5), + RealParameter("additional_seasonal_immune_population_fraction_R2", 0, 0.5,), + RealParameter("fatality_ratio_region_1", 0.0001, 0.1,), + RealParameter("fatality_rate_region_2", 0.0001, 0.1,)] +``` + +### overhaul of optimization +The support for many-objective optimization has been extensively updated, drawing on improvements +in `platypus-opt`. So is it now possible to explicitly control the seed, run multiple seeds with +one command, and control the initial population with which the optimization starts (enabling restarts). +Moreover, the way in which optimization convergence is to be assessed has changed. The workbench +now will allways store intermediate results in a tarball while `optimize` will return the final results +and any runtime convergence information coming from the algorithm. Other metrics (e.g., hypervolume) can +no longer be calculated during the run but have to be done afterwards in a post processing step. + +```python +# old +reference = Scenario("reference", b=0.4, q=2, mean=0.02, stdev=0.01) + +convergence_metrics = [ + ArchiveLogger( + "./data", + [l.name for l in lake_model.levers], + [o.name for o in lake_model.outcomes], + base_filename="lake_model_dps_archive.tar.gz", + ), + EpsilonProgress(), +] + +with MultiprocessingEvaluator(lake_model) as evaluator: + results, convergence = evaluator.optimize( + searchover="levers", + nfe=100000, + epsilons=[0.1] * len(lake_model.outcomes), + reference=reference, + convergence=convergence_metrics, + ) + +# new +reference = Sample("reference", b=0.4, q=2, mean=0.02, stdev=0.01) + +random.seed(42) +seeds = [random.randint(0, 1000) for _ in range(5)] # we run the optimization for 5 different seeds + +with MultiprocessingEvaluator(lake_model) as evaluator: + results, runtime_info = evaluator.optimize( + searchover="levers", + nfe=100000, + convergence_freq=5000, + epsilons=[0.1] * len(lake_model.outcomes), + reference=reference, + filename="lake_model_dps_archive.tar.gz", + directory="./data/convergences", + rng=seeds, + ) + + +``` + +### replacing `Policy` and `Scenario` classes with `Sample` class + + + +### Finegrained control over sampling diff --git a/docs/source/vensim-tips-and-tricks.rst b/docs/source/vensim-tips-and-tricks.rst index 6554c43a1..0e5febd75 100644 --- a/docs/source/vensim-tips-and-tricks.rst +++ b/docs/source/vensim-tips-and-tricks.rst @@ -30,9 +30,6 @@ Since the Vensim DLL does not have a way to save a model, we cannot use the DLL. Instead, we can use the fact that one can save a Vensim model as a text file. By changing the required parameters in this text file via the workbench, we can then open the modified model in Vensim and spot the error. +To make this easy, you can use `ema_worbench.connectors.vensim.create_model_for_debugging` -The following script can be used for this purpose. - -.. literalinclude:: ../../ema_workbench/examples/model_debugger.py - :linenos: From 60485e7062966f2e2a8b1fe5527d297c33414b0c Mon Sep 17 00:00:00 2001 From: Jan Kwakkel Date: Thu, 5 Mar 2026 10:48:40 +0100 Subject: [PATCH 2/3] updates --- .github/workflows/ci.yml | 4 -- docs/source/migration_guide.md | 75 +++++++++++++++++++++++++--------- pyproject.toml | 2 +- 3 files changed, 56 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6667bdc05..bca0d5e2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,10 +21,6 @@ jobs: python-version: ["3.12"] name: [""] include: - - os: ubuntu-latest - python-version: "3.10" - - os: ubuntu-latest - python-version: "3.11" - os: ubuntu-latest python-version: "3.12" pip-pre: "--pre" # Installs pre-release versions of pip dependencies diff --git a/docs/source/migration_guide.md b/docs/source/migration_guide.md index 28a0dd665..33ab6c257 100644 --- a/docs/source/migration_guide.md +++ b/docs/source/migration_guide.md @@ -3,28 +3,29 @@ # EMA Workbench Migration Guide ## Workbench 3.0 - -Version 3.0 is an extensive update of the workbench, drawing in developments in the python +Version 3.0 is an extensive update of the workbench, drawing on developments in the python ecosystem over the last decade. A large part of the API has remained the same, but there -are a number of backward incompatible changes. +are a number of backward incompatible changes. The minimum python version currently supported is +Python 3.12. When upgrading, start with addressing the naming of parameters as outlined below. Next +ensure that basic experimentation (i.e., perform_experiments) works before moving on to updating +any optimization code. On the analysis side, the most important changes are in convergence +analysis (see the notebook example on this) and the removal of `threshold` from PRIM. ### naming of parameters -Perhaps the biggest change is that parameter names now need to be valid python identifiers. -That is, you cannot simply use a number or have spaces in the names of your parameters. For +Perhaps the biggest change is that parameter names, outcomes names, constraint names, and model names now need to be valid +python identifiers. That is, you cannot simply use a number or have spaces in the names of your parameters. For those connecting to models in python, this change is not a big deal because their parameter names were already valid python identifiers. For Vensim users, the requirement that parameter names need to be valid python identifiers -might seem annoying, because the default behavior of the workbench is to make the parameter -name to a vensim variable. So, you have underscored on the python side, you'll need underscores -in vensim as well. To make life simpler for vensim users, therefore, by default, `VensimModel` +might seem annoying, because the default behavior of the workbench is to map the parameter +name to a vensim variable. So, if you use underscores instead of spaces on the python side, you'll need underscores +inside vensim as well. To make life simpler for vensim users, therefore, by default, `VensimModel` will process parameter names and replace underscores with spaces. ```python # old -model = VensimModel( - "flu", wd=r"./models/flu", model_file=r"FLUvensimV1basecase.vpm" -) +model = MyModel("flu") model.uncertainties = [ RealParameter("additional seasonal immune population fraction R1", 0, 0.5), RealParameter("additional seasonal immune population fraction R2", 0, 0.5,), @@ -32,9 +33,7 @@ model.uncertainties = [ RealParameter("fatality rate region 2", 0.0001, 0.1,)] # new -model = VensimModel( - "flu", wd=r"./models/flu", model_file=r"FLUvensimV1basecase.vpm" -) +model = MyModel() model.uncertainties = [ RealParameter("additional_seasonal_immune_population_fraction_R1", 0, 0.5), RealParameter("additional_seasonal_immune_population_fraction_R2", 0, 0.5,), @@ -44,12 +43,16 @@ model.uncertainties = [ ### overhaul of optimization The support for many-objective optimization has been extensively updated, drawing on improvements -in `platypus-opt`. So is it now possible to explicitly control the seed, run multiple seeds with +in `platypus-opt`. So, it is now possible to explicitly control the seed, run multiple seeds with one command, and control the initial population with which the optimization starts (enabling restarts). Moreover, the way in which optimization convergence is to be assessed has changed. The workbench -now will allways store intermediate results in a tarball while `optimize` will return the final results -and any runtime convergence information coming from the algorithm. Other metrics (e.g., hypervolume) can -no longer be calculated during the run but have to be done afterwards in a post processing step. +now will always store intermediate results in a tarball while `optimize` will return the final results +and any runtime convergence information coming from the algorithm. This means that `ArchiveLogger` +is no longer used and replaced with `filename`, `directory`, and `convergence_freq` keyword arguments. + +Other metrics (e.g., hypervolume) can no longer be calculated during the run but have to be done afterwards in a post processing step. +For this, the stored archives can be loaded using the new `load_archives` helper function. See the +convergence analysis notebook example for full details on how to do convergence analysis. ```python # old @@ -75,13 +78,16 @@ with MultiprocessingEvaluator(lake_model) as evaluator: ) # new +# note how we use Sample now instead of Scenario reference = Sample("reference", b=0.4, q=2, mean=0.02, stdev=0.01) random.seed(42) seeds = [random.randint(0, 1000) for _ in range(5)] # we run the optimization for 5 different seeds with MultiprocessingEvaluator(lake_model) as evaluator: - results, runtime_info = evaluator.optimize( + # we run for 5 seeds, so optimize returns a list of + # 5 tuples. Each tuple contains the results and any runtime info for one seed + all_results = evaluator.optimize( searchover="levers", nfe=100000, convergence_freq=5000, @@ -96,7 +102,36 @@ with MultiprocessingEvaluator(lake_model) as evaluator: ``` ### replacing `Policy` and `Scenario` classes with `Sample` class +In 2.x, the workbench made a distinction between a `Policy` and a `Scenario`. Under the hood, however, these objects were +identical. So, in 3.x, the distinction is dropped and, instead, it is called a Sample. We also introduced a new +`SampleCollection` class which is a collection of `Sample` instances. Likewise, `perform_experiments`, `optimize`, and +`robust_optimize` have been updated to accept `Sample`, or an iterable returning `Sample` instances. + +```python +# old +from ema_workbench import Scenario, Policy + +scenario = Scenario("some scenario", a=1, b=2) +policy = Policy("some policy", x=1, y=2) +# new +from ema_workbench import Sample +scenario = Sample("some scenario", a=1, b=2) +policy = Sample("some policy", x=1, y=2) +``` -### Finegrained control over sampling +### Fine-grained control over sampling +3.x offers more fine-grained control over sampling and samplers. First, there are new keyword arguments, +`uncertainty_sampling_kwargs` and `lever_sampling_kwargs`, which are passed on to the various sampler classes. +Second, there is more fine-grained control for combining scenarios and policies in `perform_experiments`, which +now supports `full_factorial`, `cycle`, and `sample`. `full_factorial` is the default and runs all policies for all +scenarios. `cycle` will repeat the shortest of the two deterministically until it matches the length of the longest. `sample` will +upsample with replacement the shortest of the two to match the length of the longest. Last, the SALib samplers are +now directly available from the `Samplers` enum. + +### PRIM update +The `threshold` argument has been removed from PRIM. This was causing confusion to users and was typically +not needed anyway. PRIM now always returns a box and any decision on whether the density is adequate is left +to the user. That is, a user should inspect the trade-off curve between coverage, density, and restricted dimensions and +assess which candidate box best balances these three objectives. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 9002b7b96..108d4a7b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [ description = "Exploratory modelling in Python" readme = "README.md" license = { file="LICENSE.md" } -requires-python = ">=3.10" +requires-python = ">=3.12" classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: BSD License", From 556299d4fdbd3b464af6f858b26eb2d6b866f8e8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 09:49:22 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/source/migration_guide.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/source/migration_guide.md b/docs/source/migration_guide.md index 33ab6c257..ff9278eda 100644 --- a/docs/source/migration_guide.md +++ b/docs/source/migration_guide.md @@ -5,14 +5,14 @@ ## Workbench 3.0 Version 3.0 is an extensive update of the workbench, drawing on developments in the python ecosystem over the last decade. A large part of the API has remained the same, but there -are a number of backward incompatible changes. The minimum python version currently supported is +are a number of backward incompatible changes. The minimum python version currently supported is Python 3.12. When upgrading, start with addressing the naming of parameters as outlined below. Next ensure that basic experimentation (i.e., perform_experiments) works before moving on to updating any optimization code. On the analysis side, the most important changes are in convergence analysis (see the notebook example on this) and the removal of `threshold` from PRIM. ### naming of parameters -Perhaps the biggest change is that parameter names, outcomes names, constraint names, and model names now need to be valid +Perhaps the biggest change is that parameter names, outcomes names, constraint names, and model names now need to be valid python identifiers. That is, you cannot simply use a number or have spaces in the names of your parameters. For those connecting to models in python, this change is not a big deal because their parameter names were already valid python identifiers. @@ -48,11 +48,11 @@ one command, and control the initial population with which the optimization star Moreover, the way in which optimization convergence is to be assessed has changed. The workbench now will always store intermediate results in a tarball while `optimize` will return the final results and any runtime convergence information coming from the algorithm. This means that `ArchiveLogger` -is no longer used and replaced with `filename`, `directory`, and `convergence_freq` keyword arguments. +is no longer used and replaced with `filename`, `directory`, and `convergence_freq` keyword arguments. Other metrics (e.g., hypervolume) can no longer be calculated during the run but have to be done afterwards in a post processing step. -For this, the stored archives can be loaded using the new `load_archives` helper function. See the -convergence analysis notebook example for full details on how to do convergence analysis. +For this, the stored archives can be loaded using the new `load_archives` helper function. See the +convergence analysis notebook example for full details on how to do convergence analysis. ```python # old @@ -104,7 +104,7 @@ with MultiprocessingEvaluator(lake_model) as evaluator: ### replacing `Policy` and `Scenario` classes with `Sample` class In 2.x, the workbench made a distinction between a `Policy` and a `Scenario`. Under the hood, however, these objects were identical. So, in 3.x, the distinction is dropped and, instead, it is called a Sample. We also introduced a new -`SampleCollection` class which is a collection of `Sample` instances. Likewise, `perform_experiments`, `optimize`, and +`SampleCollection` class which is a collection of `Sample` instances. Likewise, `perform_experiments`, `optimize`, and `robust_optimize` have been updated to accept `Sample`, or an iterable returning `Sample` instances. ```python @@ -122,16 +122,16 @@ policy = Sample("some policy", x=1, y=2) ``` ### Fine-grained control over sampling -3.x offers more fine-grained control over sampling and samplers. First, there are new keyword arguments, -`uncertainty_sampling_kwargs` and `lever_sampling_kwargs`, which are passed on to the various sampler classes. -Second, there is more fine-grained control for combining scenarios and policies in `perform_experiments`, which -now supports `full_factorial`, `cycle`, and `sample`. `full_factorial` is the default and runs all policies for all -scenarios. `cycle` will repeat the shortest of the two deterministically until it matches the length of the longest. `sample` will -upsample with replacement the shortest of the two to match the length of the longest. Last, the SALib samplers are +3.x offers more fine-grained control over sampling and samplers. First, there are new keyword arguments, +`uncertainty_sampling_kwargs` and `lever_sampling_kwargs`, which are passed on to the various sampler classes. +Second, there is more fine-grained control for combining scenarios and policies in `perform_experiments`, which +now supports `full_factorial`, `cycle`, and `sample`. `full_factorial` is the default and runs all policies for all +scenarios. `cycle` will repeat the shortest of the two deterministically until it matches the length of the longest. `sample` will +upsample with replacement the shortest of the two to match the length of the longest. Last, the SALib samplers are now directly available from the `Samplers` enum. -### PRIM update +### PRIM update The `threshold` argument has been removed from PRIM. This was causing confusion to users and was typically not needed anyway. PRIM now always returns a box and any decision on whether the density is adequate is left to the user. That is, a user should inspect the trade-off curve between coverage, density, and restricted dimensions and -assess which candidate box best balances these three objectives. \ No newline at end of file +assess which candidate box best balances these three objectives. \ No newline at end of file