Repository: PEtab-dev/petab_select (language: python)

Issue:
Prettify/populate sphinx-doc
https://petab-select.readthedocs.io/en/develop/ still needs some work

- [x] nicer landing page
- [x] petab-select input files #102 
- [x] link to pypesto integration example
- [x] link to other tools supporting petab-select

Get rid of petab DeprecationWarnings
With recent `petab` versions, there are lots of DeprecationWarnings when using petab-select. Let's get rid of those.

Happy to help. Maybe wait until some of the currently open PRs have been merged?
user defined criteria in enum
I was wondering, whether you could add a user defined criteria to the list of criteria a user could save with the model. For me this would be ideal, since I'd like to save the COPASI objective values (a SSR) for the individual steps. If you added a 'USER_DEFINED' or 'OTHER'  or 'SSR' or anything really to the `class Criterion(str, Enum):` in your constants, then it would make it easier for me to roundtrip those fields, rather than tracking them myself outside of `petab_select`. 


Discussion / hints:

Fix patch:
diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 260dd214..00000000
--- a/.flake8
+++ /dev/null
@@ -1,9 +0,0 @@
-########################
-# Flake8 Configuration #
-########################
-
-[flake8]
-
-per-file-ignores =
-    # Imported but unused
-    */__init__.py:F401,F403
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..edacb6be
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+version: 2
+updates:
+
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    ignore:
+      - dependency-name: "*"
+        update-types: ["version-update:semver-patch", "version-update:semver-minor"]
+    target-branch: "develop"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bd7d9253..b3182fb9 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -18,7 +18,7 @@ jobs:
     strategy:
       matrix:
         # test on latest and minimum python version
-        python-version: ['3.11', '3.9']
+        python-version: ['3.13', '3.11']
 
     steps:
     - name: Check out repository
diff --git a/.github/workflows/deploy_release.yml b/.github/workflows/deploy_release.yml
new file mode 100644
index 00000000..29347a56
--- /dev/null
+++ b/.github/workflows/deploy_release.yml
@@ -0,0 +1,34 @@
+name: Deploy
+on:
+  release:
+    types:
+      - published
+
+jobs:
+  pypi:
+    name: Deploy PyPI
+    runs-on: ubuntu-latest
+    environment:
+      name: pypi
+      url: https://pypi.org/p/petab-select
+    permissions:
+      id-token: write
+
+    steps:
+    - name: Set up Python
+      uses: actions/setup-python@v5
+      with:
+        python-version: "3.12"
+
+    - uses: actions/checkout@v4
+      with:
+        fetch-depth: 20
+
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install setuptools wheel build
+        python -m build -s
+
+    - name: Publish a Python distribution to PyPI
+      uses: pypa/gh-action-pypi-publish@release/v1
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 10ee3412..b31fc375 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -4,31 +4,8 @@
 # `pre-commit run --all-files` as by default only changed files are checked
 
 repos:
-- repo: https://github.com/psf/black
-  rev: 23.10.0
-  hooks:
-  - id: black
-    description: The uncompromising code formatter
-- repo: https://github.com/pycqa/isort
-  rev: 5.12.0
-  hooks:
-  - id: isort
-    name: isort (python)
-  - id: isort
-    name: isort (cython)
-    types: [cython]
-  - id: isort
-    name: isort (pyi)
-    types: [pyi]
-- repo: https://github.com/nbQA-dev/nbQA
-  rev: 1.7.0
-  hooks:
-    - id: nbqa-black
-    - id: nbqa-pyupgrade
-      args: [--py36-plus]
-    - id: nbqa-isort
 - repo: https://github.com/pre-commit/pre-commit-hooks
-  rev: v4.5.0
+  rev: v5.0.0
   hooks:
   - id: check-yaml
     description: Check yaml files for parseable syntax
@@ -44,3 +21,19 @@ repos:
     description: Fix empty lines at ends of files
   - id: detect-private-key
     description: Detects the presence of private keys
+- repo: https://github.com/astral-sh/ruff-pre-commit
+  # Ruff version.
+  rev: v0.6.9
+  hooks:
+    # Run the linter.
+    - id: ruff
+      args:
+        - --fix
+        - --config
+        - pyproject.toml
+
+    # Run the formatter.
+    - id: ruff-format
+      args:
+        - --config
+        - pyproject.toml
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index deef57f9..508900c4 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -9,7 +9,7 @@ version: 2
 sphinx:
   builder: html
   configuration: doc/conf.py
-  fail_on_warning: True
+  fail_on_warning: False
 
 python:
   install:
diff --git a/README.md b/README.md
index c0d87161..f4094381 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-# PEtab Select
+<img src="https://raw.githubusercontent.com/PEtab-dev/petab_select/logo/doc/logo/logo-wide.svg" height="200" alt="PEtab Select logo">
+
+[![PyPI - Version](https://img.shields.io/pypi/v/petab-select)](https://pypi.org/project/petab-select/)
 
 The repository for the development of the extension to PEtab for model
 selection, including the additional file formats and Python 3 package.
@@ -44,154 +46,3 @@ given the forward method and a predecessor model with 2 estimated parameters,
 if there are no models with 3 estimated parameters, but some models with 4
 estimated parameters, then the search may return candidate models with 4
 estimated parameters.
-
-## File formats
-
-Column or key names that are surrounding by square brackets
-(e.g. `[constraint_files]`) are optional.
-
-### Selection problem
-
-A YAML file with a description of the model selection problem.
-
-```yaml
-format_version: [string]
-criterion: [string]
-method: [string]
-model_space_files: [List of filenames]
-[constraint_files]: [List of filenames]
-[predecessor_model_files]: [List of filenames]
-```
-
-- `format_version`: The version of the model selection extension format (
-  e.g. `'beta_1'`)
-- `criterion`: The criterion by which models should be compared (e.g. `'AIC'`)
-- `method`: The method by which model candidates should be generated
-  (e.g. `'forward'`)
-- `model_space_files`: The filenames of model space files.
-- `constraint_files`: The filenames of constraint files.
-- `predecessor_model_files`: The filenames of predecessor (initial) model
-  files.
-
-### Model space
-
-A TSV with candidate models, in compressed or uncompressed format.
-
-| `model_subspace_id` | `petab_yaml` | [`sbml`] | `parameter_id_1`                                     | ... | `parameter_id_n`                                     |
-|---------------------|--------------|----------|------------------------------------------------------|-----|------------------------------------------------------|
-| (Unique) [string]   | [string]     | [string] | [string/float] OR [; delimited list of string/float] | ... | [string/float] OR [; delimited list of string/float] |
-
-- `model_subspace_id`: An ID for the model subspace.
-- `petab_yaml`: The PEtab YAML filename that serves as the base for a model.
-- `sbml`: An SBML filename. If the PEtab YAML file specifies multiple SBML
-  models, this can select a specific model by model filename.
-- `parameter_id_1`...`parameter_id_n` : Parameter IDs that are specified to
-  take specific values or be estimated. Example valid values are:
-    - uncompressed format:
-        - `0.0`
-        - `1.0`
-        - `estimate`
-    - compressed format
-        - `0.0;1.1;estimate` (the parameter can take the values `0.0` or `1.1`,
-          or be estimated according to the PEtab problem)
-
-### Constraints
-
-A TSV file with constraints.
-
-| `petab_yaml` | [`if`]                       | `constraint`                 |
-|--------------|------------------------------|------------------------------|
-| [string]     | [SBML L3 Formula expression] | [SBML L3 Formula expression] |
-
-- `petab_yaml`: The filename of the PEtab YAML file that this constraint
-  applies to.
-- `if`: As a single YAML can relate to multiple models in the model space file,
-  this ensures the constraint is only applied to the models that match
-  this `if` statement
-- `constraint`: If a model violates this constraint, it is skipped during the
-  model selection process and not optimized.
-
-### Model(s) (Predecessor models / model interchange / report)
-
-- *Predecessor models* are used to initialize an appropriate model selection
-  method. Model IDs should be unique here and compared to model IDs in any
-  model space files.
-- *Model interchange* refers to the format used to transfer model information
-  between PEtab Select and a PEtab-compatible calibration tool, during the
-  model selection process.
-- *Report* refers to the final results of the model selection process, which may
-  include calibration results from any calibrated models, or just the select
-  model.
-
-Here, the format for a single model is shown. Multiple models can be specified
-as a YAML list of the same format.
-
-The only required key is the PEtab YAML, as a model requires a PEtab problem.
-All other keys are maybe required, for the different uses of the format (e.g.,
-the report format should include `estimated_parameters`), or at different
-stages of the model selection process (the PEtab-compatible calibration tool
-should provide `criteria` for model comparison).
-
-```yaml
-[criteria]: [Dictionary of criterion names and values]
-[estimated_parameters]: [Dictionary of parameter IDs and values]
-[model_hash]: [string]
-[model_id]: [string]
-[parameters]: [Dictionary of parameter IDs and values]
-petab_yaml: [string]
-[predecessor_model_hash]: [string]
-[sbml]: [string]
-```
-
-- `criteria`: The value of the criterion by which model selection was
-  performed, at least. Optionally, other criterion values too.
-- `estimated_parameters`: Parameter estimates, not only of parameters specified
-  to be estimated in a model space file, but also parameters specified to be
-  estimated in the original PEtab problem of the model.
-- `model_hash`: The model hash, generated by the PEtab Select library.
-- `model_id`: The model ID.
-- `model_subspace_id`: Same as in the model space files.
-- `model_subspace_indices`: The indices that locate this model in its model
-  subspace.
-- `parameters`: The parameters from the problem (either values
-  or `'estimate'`) (a specific combination from a model space file, but
-  uncalibrated).
-- `petab_yaml`: Same as in model space files.
-- `predecessor_model_hash`: The hash of the model that preceded this model
-  during the model selection process.
-- `sbml`: Same as in model space files.
-
-## Test cases
-
-Several test cases are provided, to test the compatibility of a
-PEtab-compatible calibration tool with different PEtab Select features.
-
-The test cases are available in the `test_cases` directory, and are provided in
-the model format.
-
-| Test ID                                      | Criterion | Method           | Model space files | Compressed format | Constraints files | Predecessor (initial) models files |
-|----------------------------------------------|-----------|------------------|-------------------|-------------------|-------------------|------------------------------------|
-| 0001                                         | (all)     | (only one model) | 1                 |                   |                   |                                    |
-| 0002<sup>[1](#test_case_0002)</sup>          | AIC       | forward          | 1                 |                   |                   |                                    |
-| 0003                                         | BIC       | all              | 1                 | Yes               |                   |                                    |
-| 0004                                         | AICc      | backward         | 1                 |                   | 1                 |                                    |
-| 0005                                         | AIC       | forward          | 1                 |                   |                   | 1                                  |
-| 0006                                         | AIC       | forward          | 1                 |                   |                   |                                    |
-| 0007<sup>[2](#test_case_0007_and_0008)</sup> | AIC       | forward          | 1                 |                   |                   |                                    |
-| 0008<sup>[2](#test_case_0007_and_0008)</sup> | AICc      | backward         | 1                 |                   |                   |                                    |
-| 0009<sup>[3](#test_case_0009)</sup>          | AICc      | FAMoS            | 1                 | Yes               |                   | Yes                                |
-
-<a name="test_case_0002">1</a>. Model `M1_0` differs from `M1_1` in three
-parameters, but only 1 additional estimated parameter. The effect of this on
-model selection criteria needs to be clarified. Test case 0006 is a duplicate
-of 0002 that doesn't have this issue.
-
-<a name="test_case_0007_and_0008">2</a>. Noise parameter is removed, noise is
-fixed to `1`.
-
-<a name="test_case_0009">3</a>. This is a computationally expensive problem to
-solve. Developers can try a model selection initialized with the provided
-predecessor model, which is a model start that reproducibly finds the expected
-model. To solve the problem reproducibly <i>ab initio</i>, on the order of 100
-random model starts are required. This test case reproduces the model selection
-problem presented in https://doi.org/10.1016/j.cels.2016.01.002 .
diff --git a/changes.md b/changes.md
index b2f875cb..9b94daeb 100644
--- a/changes.md
+++ b/changes.md
@@ -1,5 +1,57 @@
 # Changes
 
+## 0.2.0
+There are some **major breaking changes**, to support users providing previous calibration results, e.g. from previous model selection runs. The following changes are reflected in the notebook examples.
+- **breaking change** previously, calibration tools would call `candidates` at each iteration of model selection. `candidates` has now been renamed to `start_iteration`, and tools are now expected to run `end_iteration` after calibrating the iteration's models. This structure also simplifies the codebase for other features of PEtab Select.
+- **breaking change** previously, calibration tools would determine whether to continue model selection based on whether the candidate space contains any models. Now, calibration tools should rely on the `TERMINATE` signal provided by `end_iteration` to determine whether to continue model selection.
+- **breaking change** PEtab Select hides user-calibrated models from the calibration tool, until `end_iteration` is called. Hence, if a calibration tool does some analysis on the calibrated models of the current iteration, the tool should use the `MODELS` provided by `end_iteration`, and not the `MODELS` provided by `start_iteration`.
+In summary, here's some pseudocode showing the old way.
+```python
+from petab_select.ui import candidates
+while True:
+    # Get iteration models
+    models = candidates(...).models
+    # Terminate if no models
+    if not models:
+        break
+    # Calibrate iteration models
+    for model in models:
+        calibrate(model)
+    # Print a summary/analysis of current iteration models (dummy code)
+    print_summary_of_iteration_models(models)
+```
+And here's the new way. Full working examples are given in the updated notebooks, including how to handle the candidate space.
+```python
+from petab_select.ui import start_iteration, end_iteration
+from petab_select.constants import MODELS, TERMINATE
+while True:
+    # Initialize iteration, get uncalibrated iteration models
+    iteration = start_iteration(...)
+    # Calibrate iteration models
+    for model in iteration[MODELS]:
+        calibrate(model)
+    # Finalize iteration, get all iteration models and results
+    iteration_results = end_iteration(...)
+    # Print a summary/analysis of all iteration models (dummy code)
+    print_summary_of_iteration_models(iteration_results[MODELS])
+    # Terminate if indicated
+    if iteration_results[TERMINATE]:
+        break
+```
+- Other **major changes**
+    - Many thanks to @dweindl for:
+        - documentation! https://petab-select.readthedocs.io/
+        - GitHub CI fixes and GHA deployments to PyPI and Zenodo
+    - fixed a bug introduced in 0.1.8, where FAMoS "jump to most distant" moves were not handled correctly
+    - the renamed `candidates`->`start_iteration`:
+        - no longer accepts `calibrated_models`, as they are automatically stored in the `CandidateSpace` now with each `end_iteration`
+        - `calibrated_models` and `newly_calibrated_models` no longer need to be tracked between iterations. They are now tracked by the candidate space.
+        - exclusions via `exclude_models` is no longer supported. exclusions can be supplied with `set_excluded_hashes`
+    - model hashes are more readable and composed of two parts:
+        1. the model subspace ID
+        2. the location of the model in its subspace (the model subspace indices)
+    - users can now provide model calibrations from previous model selection runs. This enables them to skip re-calibration of the same models.
+
 ## 0.1.13
 - fixed bug when no predecessor model is provided, introduced in 0.1.11 (#83)
 
diff --git a/doc/api.rst b/doc/api.rst
index 5f239f5b..6f111328 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -16,5 +16,6 @@ petab-select Python API
    petab_select.model_space
    petab_select.model_subspace
    petab_select.petab
+   petab_select.plot
    petab_select.problem
    petab_select.ui
diff --git a/doc/conf.py b/doc/conf.py
index 32fcb951..93865e8e 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -2,15 +2,19 @@
 #
 # For the full list of built-in configuration values, see the documentation:
 # https://www.sphinx-doc.org/en/master/usage/configuration.html
+from __future__ import annotations
 
 import inspect
 
+import sphinx
+
 # -- Project information -----------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 
-project = 'petab-select'
-copyright = '2023, The PEtab Select developers'
-author = 'The PEtab Select developers'
+project = "PEtab Select"
+copyright = "2024, The PEtab Select developers"
+author = "The PEtab Select developers"
+
 
 # -- General configuration ---------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -29,8 +33,8 @@
     "sphinx_autodoc_typehints",
 ]
 
-templates_path = ['_templates']
-exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+templates_path = ["_templates"]
+exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
 
 
 intersphinx_mapping = {
@@ -53,8 +57,9 @@
 # -- Options for HTML output -------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
 
-html_theme = 'sphinx_rtd_theme'
+html_theme = "sphinx_rtd_theme"
 # html_static_path = ['_static']
+html_logo = "logo/logo-wide.svg"
 
 
 def autodoc_skip_member(app, what, name, obj, skip, options):
@@ -72,5 +77,5 @@ def autodoc_skip_member(app, what, name, obj, skip, options):
     return None
 
 
-def setup(app: "sphinx.application.Sphinx"):
+def setup(app: sphinx.application.Sphinx):
     app.connect("autodoc-skip-member", autodoc_skip_member, priority=0)
diff --git a/doc/examples.rst b/doc/examples.rst
index 4147a47a..2c6c6aa3 100644
--- a/doc/examples.rst
+++ b/doc/examples.rst
@@ -7,5 +7,6 @@ Various example notebooks.
    :maxdepth: 1
 
    examples/example_cli_famos.ipynb
+   examples/visualization.ipynb
    examples/workflow_cli.ipynb
    examples/workflow_python.ipynb
diff --git a/doc/examples/calibrated_models/calibrated_models.yaml b/doc/examples/calibrated_models/calibrated_models.yaml
new file mode 100644
index 00000000..e77c5243
--- /dev/null
+++ b/doc/examples/calibrated_models/calibrated_models.yaml
@@ -0,0 +1,115 @@
+- criteria:
+    AICc: 37.97523003111246
+    NLLH: 17.48761501555623
+  estimated_parameters:
+    sigma_x2: 4.462298385653177
+  model_hash: M_0-000
+  model_id: M_0-000
+  model_subspace_id: M_0
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: 0
+    k2: 0
+    k3: 0
+  petab_yaml: petab_problem.yaml
+  predecessor_model_hash: virtual_initial_model
+- criteria:
+    AICc: -0.1754060811089051
+    NLLH: -4.0877030405544525
+  estimated_parameters:
+    k3: 0.0
+    sigma_x2: 0.12242920113658744
+  model_hash: M_1-000
+  model_id: M_1-000
+  model_subspace_id: M_1
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: 0.2
+    k2: 0.1
+    k3: estimate
+  petab_yaml: petab_problem.yaml
+  predecessor_model_hash: M_0-000
+- criteria:
+    AICc: -0.27451405630430337
+    NLLH: -4.137257028152152
+  estimated_parameters:
+    k2: 0.10147827639089564
+    sigma_x2: 0.12142256779953603
+  model_hash: M_2-000
+  model_id: M_2-000
+  model_subspace_id: M_2
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: 0.2
+    k2: estimate
+    k3: 0
+  petab_yaml: petab_problem.yaml
+  predecessor_model_hash: M_0-000
+- criteria:
+    AICc: -0.7053270517931587
+    NLLH: -4.352663525896579
+  estimated_parameters:
+    k1: 0.20160888007873565
+    sigma_x2: 0.11713858557052499
+  model_hash: M_3-000
+  model_id: M_3-000
+  model_subspace_id: M_3
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: estimate
+    k2: 0.1
+    k3: 0
+  petab_yaml: petab_problem.yaml
+  predecessor_model_hash: M_0-000
+- criteria:
+    AICc: 9.294672948206841
+    NLLH: -4.352663525896579
+  estimated_parameters:
+    k1: 0.20160888007873565
+    k3: 0.0
+    sigma_x2: 0.11713858557052499
+  model_hash: M_5-000
+  model_id: M_5-000
+  model_subspace_id: M_5
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: estimate
+    k2: 0.1
+    k3: estimate
+  petab_yaml: petab_problem.yaml
+  predecessor_model_hash: M_3-000
+- criteria:
+    AICc: 7.852170288089528
+    NLLH: -5.073914855955236
+  estimated_parameters:
+    k1: 0.20924739987621038
+    k2: 0.0859065470362628
+    sigma_x2: 0.1038731029818225
+  model_hash: M_6-000
+  model_id: M_6-000
+  model_subspace_id: M_6
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: estimate
+    k2: estimate
+    k3: 0
+  petab_yaml: petab_problem.yaml
+  predecessor_model_hash: M_3-000
diff --git a/doc/examples/calibrated_models/conditions.tsv b/doc/examples/calibrated_models/conditions.tsv
new file mode 100644
index 00000000..d6a622a6
--- /dev/null
+++ b/doc/examples/calibrated_models/conditions.tsv
@@ -0,0 +1,2 @@
+conditionId	conditionName
+model1_data1	condition1
diff --git a/doc/examples/calibrated_models/measurements.tsv b/doc/examples/calibrated_models/measurements.tsv
new file mode 100644
index 00000000..4e186f55
--- /dev/null
+++ b/doc/examples/calibrated_models/measurements.tsv
@@ -0,0 +1,7 @@
+observableId	simulationConditionId	measurement	time	noiseParameters
+obs_x2	model1_data1	0	0	sigma_x2
+obs_x2	model1_data1	0.19421762	1	sigma_x2
+obs_x2	model1_data1	0.0484032	5	sigma_x2
+obs_x2	model1_data1	0.61288016	10	sigma_x2
+obs_x2	model1_data1	4.07930835	30	sigma_x2
+obs_x2	model1_data1	10.12008893	60	sigma_x2
diff --git a/doc/examples/calibrated_models/model.xml b/doc/examples/calibrated_models/model.xml
new file mode 100644
index 00000000..878edbc4
--- /dev/null
+++ b/doc/examples/calibrated_models/model.xml
@@ -0,0 +1,315 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Created by COPASI version 4.24 (Build 197) on 2019-03-27 19:00 with libSBML version 5.17.0. -->
+<sbml xmlns="http://www.sbml.org/sbml/level2/version4" level="2" version="4">
+  <model metaid="COPASI0" id="caroModel_linear" name="caroModel">
+    <annotation>
+      <COPASI xmlns="http://www.copasi.org/static/sbml">
+        <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+          <rdf:Description rdf:about="#COPASI0">
+            <dcterms:created>
+              <rdf:Description>
+                <dcterms:W3CDTF>2019-03-27T18:47:48Z</dcterms:W3CDTF>
+              </rdf:Description>
+            </dcterms:created>
+          </rdf:Description>
+        </rdf:RDF>
+      </COPASI>
+    </annotation>
+    <listOfFunctionDefinitions>
+      <functionDefinition metaid="COPASI10" id="function_for_R3" name="function for R3">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI10">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:57:38Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+        <math xmlns="http://www.w3.org/1998/Math/MathML">
+          <lambda>
+            <bvar>
+              <ci> k3 </ci>
+            </bvar>
+            <bvar>
+              <ci> x1 </ci>
+            </bvar>
+            <bvar>
+              <ci> x2 </ci>
+            </bvar>
+            <bvar>
+              <ci> default </ci>
+            </bvar>
+            <apply>
+              <divide/>
+              <apply>
+                <times/>
+                <ci> k3 </ci>
+                <ci> x1 </ci>
+                <ci> x2 </ci>
+              </apply>
+              <ci> default </ci>
+            </apply>
+          </lambda>
+        </math>
+      </functionDefinition>
+      <functionDefinition metaid="COPASI11" id="function_for_R1" name="function for R1">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI11">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:52:47Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+        <math xmlns="http://www.w3.org/1998/Math/MathML">
+          <lambda>
+            <bvar>
+              <ci> k1 </ci>
+            </bvar>
+            <bvar>
+              <ci> default </ci>
+            </bvar>
+            <apply>
+              <divide/>
+              <ci> k1 </ci>
+              <ci> default </ci>
+            </apply>
+          </lambda>
+        </math>
+      </functionDefinition>
+      <functionDefinition metaid="COPASI12" id="function_for_R2" name="function for R2">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI12">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:55:12Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+        <math xmlns="http://www.w3.org/1998/Math/MathML">
+          <lambda>
+            <bvar>
+              <ci> k2 </ci>
+            </bvar>
+            <bvar>
+              <ci> x1 </ci>
+            </bvar>
+            <bvar>
+              <ci> default </ci>
+            </bvar>
+            <apply>
+              <divide/>
+              <apply>
+                <times/>
+                <ci> k2 </ci>
+                <ci> x1 </ci>
+              </apply>
+              <ci> default </ci>
+            </apply>
+          </lambda>
+        </math>
+      </functionDefinition>
+    </listOfFunctionDefinitions>
+    <listOfCompartments>
+      <compartment id="default" name="default" spatialDimensions="3" size="1" constant="true"/>
+    </listOfCompartments>
+    <listOfSpecies>
+      <species id="x1" name="x1" compartment="default" initialConcentration="0" boundaryCondition="false" constant="false"/>
+      <species id="x2" name="x2" compartment="default" initialConcentration="0" boundaryCondition="false" constant="false"/>
+    </listOfSpecies>
+    <listOfParameters>
+      <parameter metaid="COPASI1" id="k1" name="k1" value="0.2" constant="true">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI1">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:59:36Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+      </parameter>
+      <parameter metaid="COPASI2" id="k2" name="k2" value="0.1" constant="true">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI2">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:59:35Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+      </parameter>
+      <parameter metaid="COPASI3" id="k3" name="k3" value="0" constant="true">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI3">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:59:34Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+      </parameter>
+      <parameter metaid="COPASI4" id="sigma_x2" name="sigma_x2" value="0.04" constant="false">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI4">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:56:09Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+      </parameter>
+      <parameter metaid="COPASI6" id="observable_x2" name="observable_x2" value="0" constant="false">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI6">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:50:50Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+      </parameter>
+    </listOfParameters>
+    <listOfReactions>
+      <reaction metaid="COPASI7" id="R1" name="R1" reversible="false">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI7">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:53:29Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+        <listOfProducts>
+          <speciesReference species="x1" stoichiometry="1"/>
+        </listOfProducts>
+        <kineticLaw>
+          <math xmlns="http://www.w3.org/1998/Math/MathML">
+            <apply>
+              <times/>
+              <ci> default </ci>
+              <apply>
+                <ci> function_for_R1 </ci>
+                <ci> k1 </ci>
+                <ci> default </ci>
+              </apply>
+            </apply>
+          </math>
+        </kineticLaw>
+      </reaction>
+      <reaction metaid="COPASI8" id="R2" name="R2" reversible="false">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI8">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:55:51Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+        <listOfReactants>
+          <speciesReference species="x1" stoichiometry="1"/>
+        </listOfReactants>
+        <listOfProducts>
+          <speciesReference species="x2" stoichiometry="1"/>
+        </listOfProducts>
+        <kineticLaw>
+          <math xmlns="http://www.w3.org/1998/Math/MathML">
+            <apply>
+              <times/>
+              <ci> default </ci>
+              <apply>
+                <ci> function_for_R2 </ci>
+                <ci> k2 </ci>
+                <ci> x1 </ci>
+                <ci> default </ci>
+              </apply>
+            </apply>
+          </math>
+        </kineticLaw>
+      </reaction>
+      <reaction metaid="COPASI9" id="R3" name="R3" reversible="false">
+        <annotation>
+          <COPASI xmlns="http://www.copasi.org/static/sbml">
+            <rdf:RDF xmlns:dcterms="http://purl.org/dc/terms/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+              <rdf:Description rdf:about="#COPASI9">
+                <dcterms:created>
+                  <rdf:Description>
+                    <dcterms:W3CDTF>2019-03-27T18:49:35Z</dcterms:W3CDTF>
+                  </rdf:Description>
+                </dcterms:created>
+              </rdf:Description>
+            </rdf:RDF>
+          </COPASI>
+        </annotation>
+        <listOfReactants>
+          <speciesReference species="x1" stoichiometry="1"/>
+          <speciesReference species="x2" stoichiometry="1"/>
+        </listOfReactants>
+        <kineticLaw>
+          <math xmlns="http://www.w3.org/1998/Math/MathML">
+            <apply>
+              <times/>
+              <ci> default </ci>
+              <apply>
+                <ci> function_for_R3 </ci>
+                <ci> k3 </ci>
+                <ci> x1 </ci>
+                <ci> x2 </ci>
+                <ci> default </ci>
+              </apply>
+            </apply>
+          </math>
+        </kineticLaw>
+      </reaction>
+    </listOfReactions>
+  </model>
+</sbml>
diff --git a/doc/examples/calibrated_models/model_space.tsv b/doc/examples/calibrated_models/model_space.tsv
new file mode 100644
index 00000000..87058e5d
--- /dev/null
+++ b/doc/examples/calibrated_models/model_space.tsv
@@ -0,0 +1,9 @@
+model_subspace_id	petab_yaml	k1	k2	k3
+M_0	petab_problem.yaml	0	0	0
+M_1	petab_problem.yaml	0.2	0.1	estimate
+M_2	petab_problem.yaml	0.2	estimate	0
+M_3	petab_problem.yaml	estimate	0.1	0
+M_4	petab_problem.yaml	0.2	estimate	estimate
+M_5	petab_problem.yaml	estimate	0.1	estimate
+M_6	petab_problem.yaml	estimate	estimate	0
+M_7	petab_problem.yaml	estimate	estimate	estimate
diff --git a/doc/examples/calibrated_models/observables.tsv b/doc/examples/calibrated_models/observables.tsv
new file mode 100644
index 00000000..b3940095
--- /dev/null
+++ b/doc/examples/calibrated_models/observables.tsv
@@ -0,0 +1,2 @@
+observableId	observableFormula	noiseFormula
+obs_x2	x2	noiseParameter1_obs_x2
diff --git a/doc/examples/calibrated_models/parameters.tsv b/doc/examples/calibrated_models/parameters.tsv
new file mode 100644
index 00000000..e75b90f6
--- /dev/null
+++ b/doc/examples/calibrated_models/parameters.tsv
@@ -0,0 +1,5 @@
+parameterId	parameterName	parameterScale	lowerBound	upperBound	nominalValue	estimate
+k1	k_{1}	lin	0	1e3	0.2	1
+k2	k_{2}	lin	0	1e3	0.1	1
+k3	k_{3}	lin	0	1e3	0	1
+sigma_x2	\sigma_{x2}	lin	1e-5	1e3	0.15	1
diff --git a/doc/examples/calibrated_models/petab_problem.yaml b/doc/examples/calibrated_models/petab_problem.yaml
new file mode 100644
index 00000000..b213a304
--- /dev/null
+++ b/doc/examples/calibrated_models/petab_problem.yaml
@@ -0,0 +1,11 @@
+format_version: 1
+parameter_file: parameters.tsv
+problems:
+- condition_files:
+  - conditions.tsv
+  measurement_files:
+  - measurements.tsv
+  observable_files:
+  - observables.tsv
+  sbml_files:
+  - model.xml
diff --git a/doc/examples/calibrated_models/petab_select_problem.yaml b/doc/examples/calibrated_models/petab_select_problem.yaml
new file mode 100644
index 00000000..a26354bc
--- /dev/null
+++ b/doc/examples/calibrated_models/petab_select_problem.yaml
@@ -0,0 +1,5 @@
+version: beta_1
+criterion: AICc
+method: forward
+model_space_files:
+- model_space.tsv
diff --git a/doc/examples/example_cli_famos.ipynb b/doc/examples/example_cli_famos.ipynb
index 945a764c..5956c661 100644
--- a/doc/examples/example_cli_famos.ipynb
+++ b/doc/examples/example_cli_famos.ipynb
@@ -7,7 +7,7 @@
    "source": [
     "# FAMoS in PEtab Select\n",
     "\n",
-    "In this notebook, the FAMoS algorithm [1] is demonstrated.\n",
+    "In this notebook, the FAMoS algorithm [1] is demonstrated. This is designed as an example for tool developers to be able to ensure that they utilize PEtab Select correctly, such that complex methods like FAMoS work.\n",
     "\n",
     "[1] Gabel M, Hohl T, Imle A, Fackler OT, Graw F (2019) FAMoS: A Flexible and dynamic Algorithm for Model Selection to analyse complex systems dynamics. PLOS Computational Biology 15(8): e1007230. https://doi.org/10.1371/journal.pcbi.1007230"
    ]
@@ -32,9 +32,7 @@
     "from pathlib import Path\n",
     "\n",
     "from example_cli_famos_helpers import (\n",
-    "    expected_criterion_values,\n",
     "    parse_summary_to_progress_list,\n",
-    "    petab_select_problem_yaml,\n",
     ")\n",
     "\n",
     "output_path = Path().resolve() / \"output_famos\"\n",
@@ -50,16 +48,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "import numpy as np\n",
-    "import pandas as pd\n",
-    "import petab\n",
+    "from petab_select import Method\n",
     "\n",
-    "import petab_select\n",
-    "from petab_select import ESTIMATE, FamosCandidateSpace, Method, Model\n",
-    "from petab_select.constants import Criterion\n",
-    "from petab_select.model import default_compare\n",
-    "\n",
-    "state = str(output_path / 'state.dill')\n",
+    "state = str(output_path / \"state.dill\")\n",
     "\n",
     "# Each iteration of model selection is described as a 2-tuple here.\n",
     "# First value is the model selection method.\n",
@@ -120,7 +111,66 @@
    "execution_count": 3,
    "id": "bb1a5144",
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Executing iteration 1\n",
+      "Executing iteration 2\n",
+      "Executing iteration 3\n",
+      "Executing iteration 4\n",
+      "Executing iteration 5\n",
+      "Executing iteration 6\n",
+      "Executing iteration 7\n",
+      "Executing iteration 8\n",
+      "Executing iteration 9\n",
+      "Executing iteration 10\n",
+      "Executing iteration 11\n",
+      "Executing iteration 12\n",
+      "Executing iteration 13\n",
+      "Executing iteration 14\n",
+      "Executing iteration 15\n",
+      "Executing iteration 16\n",
+      "Executing iteration 17\n",
+      "Executing iteration 18\n",
+      "Executing iteration 19\n"
+     ]
+    },
+    {
+     "name": "stderr",
+     "output_type": "stream",
+     "text": [
+      "/home/dilan/Documents/future_annex/model_selection/packages/petab_select/petab_select/candidate_space.py:376: RuntimeWarning: Model `model_subspace_1-0001011010010010` has been previously excluded from the candidate space so is skipped here.\n",
+      "  warnings.warn(\n"
+     ]
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Executing iteration 20\n",
+      "Executing iteration 21\n",
+      "Executing iteration 22\n",
+      "Executing iteration 23\n",
+      "Executing iteration 24\n",
+      "Executing iteration 25\n",
+      "Executing iteration 26\n",
+      "Executing iteration 27\n",
+      "Executing iteration 28\n",
+      "Executing iteration 29\n",
+      "Executing iteration 30\n",
+      "Executing iteration 31\n",
+      "Executing iteration 32\n",
+      "Executing iteration 33\n",
+      "Executing iteration 34\n",
+      "Executing iteration 35\n",
+      "Executing iteration 36\n",
+      "Executing iteration 37\n",
+      "Model selection has terminated.\n"
+     ]
+    }
+   ],
    "source": [
     "%%bash -s \"$petab_select_problem_yaml\" \"$output_path_str\"\n",
     "\n",
@@ -130,37 +180,38 @@
     "problem=$petab_select_problem_yaml\n",
     "state=$output_path_str/state.dill\n",
     "\n",
-    "newly_calibrated_models=\"\"\n",
-    "calibrated_models=\"\"\n",
-    "empty_output=$(echo -e \"null\\n...\")\n",
+    "file_uncalibrated_models=$output_path_str/uncalibrated_models.yaml\n",
+    "file_calibrated_models=$output_path_str/calibrated_models.yaml\n",
+    "file_models=$output_path_str/iteration_models.yaml\n",
+    "file_metadata=$output_path_str/metadata.yaml\n",
     "\n",
     "for i in {1..40}\n",
     "do\n",
+    "    echo \"Executing iteration $i\"\n",
     "\n",
-    "output=$output_path_str/models_$i.yaml\n",
-    "calibrated_output=$output_path_str/calibrated_models_$i.yaml\n",
-    "\n",
-    "petab_select candidates \\\n",
-    "--problem $problem \\\n",
-    "--state $state \\\n",
-    "--output $output \\\n",
-    "$newly_calibrated_models \\\n",
-    "$calibrated_models \\\n",
-    "--relative-paths\n",
-    "\n",
-    "# Replace this line with a tool that calibrates the models.\n",
-    "# The script also changes model IDs for easier analysis in this example.\n",
-    "python example_cli_famos_calibration_tool.py $output $calibrated_output\n",
-    "\n",
-    "if [ \"$(cat $calibrated_output)\" == \"$empty_output\" ]\n",
-    "then\n",
-    "        # End the model selection if no models were provided by PEtab Select.\n",
+    "    petab_select start_iteration \\\n",
+    "    --problem=\"$problem\" \\\n",
+    "    --state=\"$state\" \\\n",
+    "    --output-uncalibrated-models=\"$file_uncalibrated_models\" \\\n",
+    "    --relative-paths\n",
+    "    \n",
+    "    # Replace this line with a tool that calibrates the models in `$output`\n",
+    "    # and stores the calibrated models in `$calibrated_output`.\n",
+    "    # This script also changes model IDs for easier analysis in this example.\n",
+    "    python example_cli_famos_calibration_tool.py $file_uncalibrated_models $file_calibrated_models\n",
+    "    \n",
+    "    petab_select end_iteration \\\n",
+    "    --state=\"$state\" \\\n",
+    "    --calibrated-models=\"$file_calibrated_models\" \\\n",
+    "    --output-models=\"$file_models\" \\\n",
+    "    --output-metadata=\"$file_metadata\" \\\n",
+    "    --relative-paths\n",
+    "    \n",
+    "    terminate=$(cat $file_metadata | grep terminate | awk '{print $NF}')\n",
+    "    if [ \"$terminate\" = \"true\" ]; then\n",
+    "        echo \"Model selection has terminated.\"\n",
     "        break\n",
-    "else\n",
-    "        newly_calibrated_models=\"--newly-calibrated-models \"$calibrated_output\n",
-    "        calibrated_models+=\"--calibrated-models \"$calibrated_output\" \"\n",
-    "fi\n",
-    "\n",
+    "    fi\n",
     "done"
    ]
   },
@@ -171,7 +222,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "progress_list = parse_summary_to_progress_list('output_famos/summary.tsv')"
+    "progress_list = parse_summary_to_progress_list(output_path / \"summary.tsv\")"
    ]
   },
   {
@@ -187,7 +238,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -201,7 +252,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.10"
+   "version": "3.12.3"
   }
  },
  "nbformat": 4,
diff --git a/doc/examples/example_cli_famos_calibration_tool.py b/doc/examples/example_cli_famos_calibration_tool.py
index 1f589fba..c78cabbe 100644
--- a/doc/examples/example_cli_famos_calibration_tool.py
+++ b/doc/examples/example_cli_famos_calibration_tool.py
@@ -1,10 +1,8 @@
 import sys
 
-import pandas as pd
-from example_cli_famos_helpers import calibrate, set_model_id
+from example_cli_famos_helpers import calibrate
 
 import petab_select
-from petab_select import ESTIMATE, Criterion, Model
 
 models_yaml = sys.argv[1]
 calibrated_models_yaml = sys.argv[2]
@@ -12,21 +10,18 @@
 models = petab_select.model.models_from_yaml_list(models_yaml)
 predecessor_model_hashes = set()
 for model in models:
-    set_model_id(model=model)
     calibrate(model=model)
     predecessor_model_hashes |= {model.predecessor_model_hash}
 petab_select.model.models_to_yaml_list(
     models=models, output_yaml=calibrated_models_yaml
 )
 
-model_ids = sorted(model.model_id for model in models)
-
 if len(predecessor_model_hashes) == 0:
     pass
 elif len(predecessor_model_hashes) == 1:
     (predecessor_model_hash,) = predecessor_model_hashes
 else:
     print(
-        'The models of this iteration somehow have different predecessor models.\n'
-        + '\n'.join(predecessor_model_hashes)
+        "The models of this iteration somehow have different predecessor models.\n"
+        + "\n".join(predecessor_model_hashes)
     )
diff --git a/doc/examples/example_cli_famos_helpers.py b/doc/examples/example_cli_famos_helpers.py
index 05f57686..532950cd 100644
--- a/doc/examples/example_cli_famos_helpers.py
+++ b/doc/examples/example_cli_famos_helpers.py
@@ -1,10 +1,10 @@
 from pathlib import Path
-from typing import List, Tuple
 
 import pandas as pd
+from more_itertools import one
 
 import petab_select
-from petab_select import ESTIMATE, Method, Model
+from petab_select import MODEL_HASH, Criterion, Method, Model
 
 input_path = (
     Path(__file__).resolve().parent.parent.parent
@@ -19,7 +19,7 @@
 expected_criterion_values = dict(
     pd.read_csv(
         input_path / "test_files" / "calibration_results.tsv", sep="\t"
-    ).set_index('model_id')['AICc']
+    ).set_index(MODEL_HASH)[Criterion.AICC]
 )
 
 petab_select_problem = petab_select.Problem.from_yaml(
@@ -28,13 +28,6 @@
 criterion = petab_select_problem.criterion
 
 
-def set_model_id(model: Model) -> None:
-    """Set the model ID to a binary string for easier analysis."""
-    model.model_id = "M_" + ''.join(
-        str(v) for v in model.model_subspace_indices
-    )
-
-
 def calibrate(
     model: Model,
     criterion=criterion,
@@ -43,21 +36,21 @@ def calibrate(
     """Set the criterion value for a model."""
     model.set_criterion(
         criterion=criterion,
-        value=float(expected_criterion_values[model.model_id]),
+        value=float(expected_criterion_values[model.get_hash()]),
     )
 
 
 def parse_summary_to_progress_list(
     summary_tsv: str,
-) -> List[Tuple[Method, set]]:
+) -> list[tuple[Method, set]]:
     """Get progress information from the summary file."""
-    df_raw = pd.read_csv(summary_tsv, sep='\t')
+    df_raw = pd.read_csv(summary_tsv, sep="\t")
     df = df_raw.loc[~pd.isnull(df_raw["predecessor change"])]
 
     parameter_list = list(
-        petab_select_problem.model_space.model_subspaces[
-            'model_subspace_1'
-        ].parameters
+        one(
+            petab_select_problem.model_space.model_subspaces.values()
+        ).parameters
     )
 
     progress_list = []
diff --git a/doc/examples/model_selection/calibrated_M1_4.yaml b/doc/examples/model_selection/calibrated_M1_4.yaml
index 90de1f16..b64f141b 100644
--- a/doc/examples/model_selection/calibrated_M1_4.yaml
+++ b/doc/examples/model_selection/calibrated_M1_4.yaml
@@ -1,11 +1,18 @@
 criteria:
-  AIC: 30
-model_id: M1_4_0
-parameters:
-  k1: 0.2
-  k2: estimate
-  k3: estimate
+  AIC: 15
 estimated_parameters:
   k2: 0.15
   k3: 0.0
+model_hash: M1_4-000
+model_id: M1_4-000
+model_subspace_id: M1_4
+model_subspace_indices:
+- 0
+- 0
+- 0
+parameters:
+  k1: 0
+  k2: estimate
+  k3: estimate
 petab_yaml: ../model_selection/petab_problem.yaml
+predecessor_model_hash: null
diff --git a/doc/examples/model_selection/calibrated_M1_7.yaml b/doc/examples/model_selection/calibrated_M1_7.yaml
index fdcfc217..a3829482 100644
--- a/doc/examples/model_selection/calibrated_M1_7.yaml
+++ b/doc/examples/model_selection/calibrated_M1_7.yaml
@@ -1,12 +1,19 @@
 criteria:
   AIC: 20
-model_id: M1_7_0
-parameters:
-  k1: estimate
-  k2: estimate
-  k3: estimate
 estimated_parameters:
   k1: 0.25
   k2: 0.1
   k3: 0.0
+model_hash: M1_7-000
+model_id: M1_7-000
+model_subspace_id: M1_7
+model_subspace_indices:
+- 0
+- 0
+- 0
+parameters:
+  k1: estimate
+  k2: estimate
+  k3: estimate
 petab_yaml: ../model_selection/petab_problem.yaml
+predecessor_model_hash: null
diff --git a/doc/examples/model_selection/calibrated_first_iteration.yaml b/doc/examples/model_selection/calibrated_first_iteration.yaml
deleted file mode 100644
index 97d024de..00000000
--- a/doc/examples/model_selection/calibrated_first_iteration.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-- criteria:
-    AIC: 180
-  model_id: M1_0
-  parameters:
-    k1: 0.0
-    k2: 0.0
-    k3: 0.0
-  estimated_parameters: null
-  petab_yaml: ../model_selection/petab_problem.yaml
-- criteria:
-    AIC: 100
-  model_id: M1_1
-  parameters:
-    k1: 0.2
-    k2: 0.1
-    k3: estimate
-  estimated_parameters:
-    k3: 0.0
-  petab_yaml: ../model_selection/petab_problem.yaml
-- criteria:
-    AIC: 50
-  model_id: M1_2
-  parameters:
-    k1: 0.2
-    k2: estimate
-    k3: 0.0
-  estimated_parameters:
-    k2: 0.05
-  petab_yaml: ../model_selection/petab_problem.yaml
diff --git a/doc/examples/model_selection/calibrated_models_1.yaml b/doc/examples/model_selection/calibrated_models_1.yaml
new file mode 100644
index 00000000..f34ae7bb
--- /dev/null
+++ b/doc/examples/model_selection/calibrated_models_1.yaml
@@ -0,0 +1,48 @@
+- criteria:
+    AIC: 180
+  estimated_parameters: {}
+  model_hash: M1_0-000
+  model_id: M1_0-000
+  model_subspace_id: M1_0
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: 0
+    k2: 0
+    k3: 0
+  petab_yaml: ../model_selection/petab_problem.yaml
+  predecessor_model_hash: null
+- criteria:
+    AIC: 100
+  estimated_parameters: {}
+  model_hash: M1_1-000
+  model_id: M1_1-000
+  model_subspace_id: M1_1
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: 0.2
+    k2: 0.1
+    k3: estimate
+  petab_yaml: ../model_selection/petab_problem.yaml
+  predecessor_model_hash: null
+- criteria:
+    AIC: 50
+  estimated_parameters: {}
+  model_hash: M1_2-000
+  model_id: M1_2-000
+  model_subspace_id: M1_2
+  model_subspace_indices:
+  - 0
+  - 0
+  - 0
+  parameters:
+    k1: 0.2
+    k2: estimate
+    k3: 0
+  petab_yaml: ../model_selection/petab_problem.yaml
+  predecessor_model_hash: null
diff --git a/doc/examples/visualization.ipynb b/doc/examples/visualization.ipynb
new file mode 100644
index 00000000..8b86ad63
--- /dev/null
+++ b/doc/examples/visualization.ipynb
@@ -0,0 +1,458 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "50d3669e",
+   "metadata": {},
+   "source": [
+    "# Visualization gallery"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c01e724e-1d4e-41e7-bb76-742f354fd15f",
+   "metadata": {},
+   "source": [
+    "The visualization methods implemented in PEtab Select are demonstrated here. These methods generally visualize the output of a model selection task, so the input is generally a list of already-calibrated models.\n",
+    "\n",
+    "All dependencies for these plots can be installed with `pip install petab_select[plot]`.\n",
+    "\n",
+    "Here, some calibrated models that were saved to disk with `petab_select.model.models_to_yaml_list` are loaded and used as input. This is the result of a forward selection with the problem provided in `calibrated_models`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "ca6ce5b4",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import petab_select\n",
+    "import petab_select.plot\n",
+    "\n",
+    "models = petab_select.models_from_yaml_list(\n",
+    "    model_list_yaml=\"calibrated_models/calibrated_models.yaml\"\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "2574e65a-1f16-4205-8c23-b65ba78f9a1a",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<style type=\"text/css\">\n",
+       "#T_1ba7c_row0_col1 {\n",
+       "  background-color: #ffff66;\n",
+       "  color: #000000;\n",
+       "}\n",
+       "#T_1ba7c_row1_col1 {\n",
+       "  background-color: #038166;\n",
+       "  color: #f1f1f1;\n",
+       "}\n",
+       "#T_1ba7c_row2_col1 {\n",
+       "  background-color: #028066;\n",
+       "  color: #f1f1f1;\n",
+       "}\n",
+       "#T_1ba7c_row3_col1 {\n",
+       "  background-color: #008066;\n",
+       "  color: #f1f1f1;\n",
+       "}\n",
+       "#T_1ba7c_row4_col1 {\n",
+       "  background-color: #42a066;\n",
+       "  color: #f1f1f1;\n",
+       "}\n",
+       "#T_1ba7c_row5_col1 {\n",
+       "  background-color: #389c66;\n",
+       "  color: #f1f1f1;\n",
+       "}\n",
+       "</style>\n",
+       "<table id=\"T_1ba7c\">\n",
+       "  <thead>\n",
+       "    <tr>\n",
+       "      <th class=\"blank level0\" >&nbsp;</th>\n",
+       "      <th id=\"T_1ba7c_level0_col0\" class=\"col_heading level0 col0\" >Model hash</th>\n",
+       "      <th id=\"T_1ba7c_level0_col1\" class=\"col_heading level0 col1\" >AICc criterion</th>\n",
+       "      <th id=\"T_1ba7c_level0_col2\" class=\"col_heading level0 col2\" >Predecessor model hash</th>\n",
+       "      <th id=\"T_1ba7c_level0_col3\" class=\"col_heading level0 col3\" >Estimated parameters</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th id=\"T_1ba7c_level0_row0\" class=\"row_heading level0 row0\" >0</th>\n",
+       "      <td id=\"T_1ba7c_row0_col0\" class=\"data row0 col0\" >M_0-000</td>\n",
+       "      <td id=\"T_1ba7c_row0_col1\" class=\"data row0 col1\" >37.975230</td>\n",
+       "      <td id=\"T_1ba7c_row0_col2\" class=\"data row0 col2\" >virtual_initial_model-</td>\n",
+       "      <td id=\"T_1ba7c_row0_col3\" class=\"data row0 col3\" >sigma_x2</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th id=\"T_1ba7c_level0_row1\" class=\"row_heading level0 row1\" >1</th>\n",
+       "      <td id=\"T_1ba7c_row1_col0\" class=\"data row1 col0\" >M_1-000</td>\n",
+       "      <td id=\"T_1ba7c_row1_col1\" class=\"data row1 col1\" >-0.175406</td>\n",
+       "      <td id=\"T_1ba7c_row1_col2\" class=\"data row1 col2\" >M_0-000</td>\n",
+       "      <td id=\"T_1ba7c_row1_col3\" class=\"data row1 col3\" >k3, sigma_x2</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th id=\"T_1ba7c_level0_row2\" class=\"row_heading level0 row2\" >2</th>\n",
+       "      <td id=\"T_1ba7c_row2_col0\" class=\"data row2 col0\" >M_2-000</td>\n",
+       "      <td id=\"T_1ba7c_row2_col1\" class=\"data row2 col1\" >-0.274514</td>\n",
+       "      <td id=\"T_1ba7c_row2_col2\" class=\"data row2 col2\" >M_0-000</td>\n",
+       "      <td id=\"T_1ba7c_row2_col3\" class=\"data row2 col3\" >k2, sigma_x2</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th id=\"T_1ba7c_level0_row3\" class=\"row_heading level0 row3\" >3</th>\n",
+       "      <td id=\"T_1ba7c_row3_col0\" class=\"data row3 col0\" >M_3-000</td>\n",
+       "      <td id=\"T_1ba7c_row3_col1\" class=\"data row3 col1\" >-0.705327</td>\n",
+       "      <td id=\"T_1ba7c_row3_col2\" class=\"data row3 col2\" >M_0-000</td>\n",
+       "      <td id=\"T_1ba7c_row3_col3\" class=\"data row3 col3\" >k1, sigma_x2</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th id=\"T_1ba7c_level0_row4\" class=\"row_heading level0 row4\" >4</th>\n",
+       "      <td id=\"T_1ba7c_row4_col0\" class=\"data row4 col0\" >M_5-000</td>\n",
+       "      <td id=\"T_1ba7c_row4_col1\" class=\"data row4 col1\" >9.294673</td>\n",
+       "      <td id=\"T_1ba7c_row4_col2\" class=\"data row4 col2\" >M_3-000</td>\n",
+       "      <td id=\"T_1ba7c_row4_col3\" class=\"data row4 col3\" >k1, k3, sigma_x2</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th id=\"T_1ba7c_level0_row5\" class=\"row_heading level0 row5\" >5</th>\n",
+       "      <td id=\"T_1ba7c_row5_col0\" class=\"data row5 col0\" >M_6-000</td>\n",
+       "      <td id=\"T_1ba7c_row5_col1\" class=\"data row5 col1\" >7.852170</td>\n",
+       "      <td id=\"T_1ba7c_row5_col2\" class=\"data row5 col2\" >M_3-000</td>\n",
+       "      <td id=\"T_1ba7c_row5_col3\" class=\"data row5 col3\" >k1, k2, sigma_x2</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n"
+      ],
+      "text/plain": [
+       "<pandas.io.formats.style.Styler at 0x785098b50320>"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "import matplotlib\n",
+    "import pandas as pd\n",
+    "\n",
+    "pd.DataFrame(\n",
+    "    {\n",
+    "        \"Model hash\": [model.model_id for model in models],\n",
+    "        \"AICc criterion\": [\n",
+    "            model.get_criterion(petab_select.Criterion.AICC)\n",
+    "            for model in models\n",
+    "        ],\n",
+    "        \"Predecessor model hash\": [\n",
+    "            model.predecessor_model_hash for model in models\n",
+    "        ],\n",
+    "        \"Estimated parameters\": [\n",
+    "            \", \".join(model.get_estimated_parameter_ids_all())\n",
+    "            for model in models\n",
+    "        ],\n",
+    "    }\n",
+    ").style.background_gradient(\n",
+    "    cmap=matplotlib.colormaps.get_cmap(\"summer\"),\n",
+    "    subset=[\"AICc criterion\"],\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "aaeb0606",
+   "metadata": {},
+   "source": [
+    "We customize the labels here to just be the second part of a model hash: a binary string that describes their estimated parameters. We additionally set a custom color for a couple of models, and the default color for the other models. We also set a nicer label for the \"virtual initial model\", which is a hypothetical model that PEtab Select uses by default to initialize a forward search (with no parameters)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "09c9df1d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Custom labels\n",
+    "labels = {}\n",
+    "for model in models:\n",
+    "    labels[model.get_hash()] = \"M_\" + \"\".join(\n",
+    "        \"1\" if value == petab_select.ESTIMATE else \"0\"\n",
+    "        for value in model.parameters.values()\n",
+    "    )\n",
+    "labels[petab_select.ModelHash(petab_select.VIRTUAL_INITIAL_MODEL, \"\")] = (\n",
+    "    \"\\n\".join(petab_select.VIRTUAL_INITIAL_MODEL.split(\"_\")).title()\n",
+    ")\n",
+    "\n",
+    "# Custom colors for some models\n",
+    "colors = {\n",
+    "    \"M_000\": \"lightgreen\",\n",
+    "    \"M_001\": \"lightgreen\",\n",
+    "}\n",
+    "\n",
+    "# Change default color\n",
+    "petab_select.plot.NORMAL_NODE_COLOR = \"darkgray\""
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6c73e0bc-5bf8-4c03-a54b-f19ced731322",
+   "metadata": {},
+   "source": [
+    "## UpSet plot\n",
+    "\n",
+    "This shows models ordered by criterion, with their parameters directly below the bars.\n",
+    "\n",
+    "A black dot indicates that the parameter (e.g `k2`) is estimated in the model (e.g. the first bar is a model with `k1` and `sigma_x2` estimated)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "96d99572-f74d-4e25-8237-0aa158eb29f6",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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",
+      "text/plain": [
+       "<Figure size 400x444.444 with 3 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "petab_select.plot.upset(models=models, criterion=petab_select.Criterion.AICC);"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "32de6556",
+   "metadata": {},
+   "source": [
+    "## Selected models\n",
+    "\n",
+    "This shows strict improvements in the criterion, and the corresponding model, across all iterations of model selection.\n",
+    "\n",
+    "Since there were no improvements after `M_100`, no other iterations or models are shown."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "56b4a27b",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAdgAAAFjCAYAAACe1BovAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA7NklEQVR4nO3deXhU9b0/8PfMJDPZM2TfAwFCEkP2GQQRUILRol0UrLXV2qfI7dVKFf3VtbXaKqWI1eq9Vu6lLlXvreK13ktr2V0iykx2sgdIyL6TPZlMZs7vD5qReE5gEjJb5v16nnlKzuebMx8qOe98z5zzPTJBEAQQERHRnJI7ugEiIqL5iAFLRERkAwxYIiIiG2DAEhER2QADloiIyAYYsERERDbAgCUiIrIBtw9YQRAwMDAA3g5MRERzye0DdnBwEIGBgRgcHHR0K0RENI+4fcASERHZAgOWiIjIBhiwRERENsCAJSIisgEGLBERkQ0wYImIiGyAAUtERGQDDFgiIiIbYMASERHZAAOWiIjIBhiwRERENsCAJSIisgEGLBERkQ0wYImIiGyAAUtERGQDDFgiIiIbYMASERHZAAOWiIjIBhiwRERENsCAJSIisgEGLBERkQ0wYImIiGyAAUtERGQDDFgiIiIbYMASERHZgNMH7DPPPAOZTIbU1FRR7fjx41i9ejV8fHwQERGBbdu2YWhoyAFdEhERTeXh6AYuprm5Gc8++yx8fX1FtZKSEqxfvx7Jycl4/vnn0dzcjOeeew51dXX46KOPHNAtERHRV5w6YB966CFceeWVMJlM6O7unlJ77LHHsGDBAnz88ccICAgAACxcuBB33303Dh48iOuuu84RLRMREQFw4lPEn376Kfbt24cXXnhBVBsYGMChQ4fwgx/8wBKuAHDnnXfCz88P7777rh07JSIiEnPKGazJZMJ9992HLVu2YPny5aL6yZMnMTExgZycnCnblUolMjIyUFxcPO2+DQYDDAaD5euBgYG5a5yIiOifnHIG+8c//hFnz57Fr3/9a8l6W1sbACAyMlJUi4yMRGtr67T73rFjBwIDAy2v2NjYmTeYn2/b8URE5PKcLmB7enrwy1/+Er/4xS8QGhoqOWZ0dBQAoFKpRDUvLy9LXcqjjz6K/v5+y6upqWlmDf7qV8DVVwM7d1o3fufO8+N/9auZvQ8REbk0pztF/MQTTyAoKAj33XfftGO8vb0BYMqp3kljY2OWuhSVSiUZzFbJzweeeur8nx955Pz/Pvzw9ON37vxq3FNPAbm5wOrVs3tvIiJyKU41g62rq8OePXuwbds2tLa2oqGhAQ0NDRgbG4PRaERDQwN6e3stp4YnTxVfqK2tDVFRUbZpcPVq4Le//errRx6ZfiZ7YbgC57+P4UpE5DacKmBbWlpgNpuxbds2LFq0yPI6ceIEamtrsWjRIjz99NNITU2Fh4cHCgoKpnz/+Pg4SkpKkJGRYbsmH3740iErFa4Xm+kSEdG841SniFNTU/HBBx+Itj/xxBMYHBzEiy++iMWLFyMwMBC5ubl466238Itf/AL+/v4AgD//+c8YGhrC5s2bbdvoZFhOhugjj6CrqwsBzzwD1QsvMFyJiAgyQRAERzdxKevWrUN3dzfKy8st24qKirBq1SqkpKRg69ataG5uxu7du7FmzRocOHDA6n0PDAwgMDAQ/f39U+6ptcrXZqrj/v5QDg5+VWe4EhG5Lac6RTwTWVlZOHz4MLy9vfHAAw9gz549+PGPf4x9+/bZr4mHH4bxgluJLgzXiWeeYbgSEbkxl5jB2tJlzWABfPnll8jcsAGqCx4yYPDzw8F33kFOTo7kvbpERDT/OdVnsK4o+/BheH7tCT6qoSHE//d/47PRUSQkJCA9PR2enp4O6pCIiByBAXs5du6E5y9+Yfly3M8Pyn+Gbdo77wAAqr/1LbS3t0Or1SIsLMwhbRIRkf257GewDidxK85ERwfObN1q2ZT2zjtI+vBDjIyM4OOPP0ZRUREmJiYc0CwREdkbA3Y2prnP1cfHB4v++Ef0/L//ZylNhiwAnDp1CgcPHhQ9eo+IiOYfBuxMXWIRCZlMhuDf/Q6GySUVMTVkh4aGcPToUZSWlsJkMtmtbSIisi8G7Ezk51u9iITql7+EsGOH5eu0d95BSHW15euamhocOnQIvb29NmuXiIgchwE7E6tXA08+ef7PViwiIXvkEcuyimfuuAPdSUlT6gMDAzhy5AhOnjzJ2SwR0TzD+2Bncx9sfv7MFu7Pz4d51SrU1taivLwcZrNZNCQwMBArVqyAWq22fr9EROS0GLCXudDETPX390On0+HcuXOimkwmQ0pKCpKTkyGX8+QCEZErY8DaOWABwGw2o6qqCpWVlZD6v3/BggXQarUIDAy0Sz9ERDT3GLAOCNhJ586dg06nQ39/v6gml8uRmpqKxMREzmaJiFwQA9aBAQsAJpMJlZWVqK6ulpzNBgcHQ6vVWh7JR0REroEB6+CAndTT0wOdTofBCx93908KhQJpaWlYsmQJZDKZA7ojIqKZYsA6ScACwMTEBMrLy1FbWytZDwsLg0ajga+vr507IyKimWLAOlHATurq6oJer8fQ157SAwAeHh5IT09HQkICZ7NERE6MAeuEAQucn82WlZXh1KlTkvWIiAjk5OTAx8fHzp0REZE1GLBOGrCTOjo6oNfrMTIyIqp5enoiMzMT8fHxnM0SETkZBqyTBywAGI1GlJSUoL6+XrIeFRWF7OxseHt727kzIiKaDgPWBQJ2UltbGwoKCjA6OiqqKZVKZGVlIS4uzgGdERHR1zFgXShgAWB8fBzFxcU4e/asZD0mJgbZ2dlQqVR27oyIiC7EgHWxgJ3U0tKCgoICGAwGUU2lUiEnJwfR0dEO6IyIiAAGrMsGLACMjY2hqKgIzc3NkvX4+HhkZmZCqVTauTMiImLAunDATmpsbERRURHGx8dFNW9vb+Tk5CAyMtIBnRERuS8G7DwIWAAYHR1FYWEhWltbJeuLFi1CRkYGPD097dwZEZF7YsDOk4AFAEEQcPbsWRQXF8NoNIrqPj4+0Gg0CA8Pd0B3RETuhQE7jwJ20sjICPR6PTo6OiTrS5YsQVpaGjw8POzcGRGR+2DAzsOABc7PZs+cOYPS0lJMTEyI6n5+ftBqtQgJCXFAd0RE8x8Ddp4G7KShoSHo9Xp0dXVJ1hMTE5GamsrZLBHRHGPAzvOABc7PZuvq6nDy5EmYTCZR3d/fH1qtFsHBwQ7ojohofmLAukHAThocHIROp0NPT4+oJpPJkJSUhJSUFCgUCgd0R0Q0vzBg3ShgAcBsNqO2thbl5eUwm82iemBgILRaLRYsWOCA7oiI5g8GrJsF7KT+/n7odDqcO3dOVJPJZEhJSUFycjLkcrkDuiMicn0MWDcNWOD8bLa6uhoVFRWQ+mewYMECaLVaBAYGOqA7IiLXxoB144CddO7cOeh0OvT394tqcrkcqampSExM5GyWiGgGGLAMWACAyWRCZWUlqqurJWezwcHB0Gq18Pf3d0B3RESuhwHLgJ2it7cXOp0OAwMDoppCocDy5cuxdOlSyGQyB3RHROQ6GLAMWBGTyYTy8nLU1NRI1kNDQ6HRaODn52fnzoiIXAcDlgE7ra6uLuj1egwNDYlqHh4eSE9PR0JCAmezREQSGLAM2IuamJhAWVkZTp06JVkPDw+HRqOBj4+PnTsjInJuDFgGrFU6Ojqg1+sxMjIiqnl6eiIjIwMLFy7kbJaI6J8YsAxYqxmNRpSWluLMmTOS9aioKGRnZ8Pb29vOnREROR8GLAN2xtra2lBQUIDR0VFRTalUIisrC7GxsZzNEpFbY8AyYGdlfHwcxcXFOHv2rGQ9JiYGWVlZ8PLysnNnRETOgQHLgL0sLS0tKCgogMFgENVUKhWys7MRExPjgM6IiByLAcuAvWwGgwGFhYVobm6WrMfHxyMzMxNKpdLOnREROQ4DlgE7ZxobG1FUVITx8XFRzdvbGzk5OYiMjHRAZ0RE9seAZcDOqdHRURQWFqK1tVWyvmjRImRkZMDT09POnRER2ZfTPR6loqICmzdvRkJCAnx8fBASEoI1a9bg//7v/0Rjq6qqcP3118PPzw9BQUG444470NXV5YCuaZK3tzeuuuoqaLVayRCtr6/HgQMH0NHR4YDuiIjsx+lmsH//+9/xhz/8AStXrkRUVBRGRkbw/vvv47PPPsOrr76KrVu3AgCam5uRmZmJwMBAbNu2DUNDQ3juuecQFxcHnU5n9ed9nMHazsjICAoKCtDe3i5ZX7JkCdLS0uDh4WHnzoiIbM/pAlaKyWRCdnY2xsbGUF1dDQC455578Prrr6O6uhpxcXEAgMOHD2PDhg1TgvhSGLC2JQgCzpw5g9LSUkxMTIjqfn5+0Gg0CA0NdUB3RES243SniKUoFArExsair6/Psu3999/HjTfeaAlXAMjNzUViYiLeffddB3RJUmQyGRYvXoy8vDzJEB0aGsKxY8dQUlIiGcBERK7KaQN2eHgY3d3dOH36NH7/+9/jo48+wvr16wGcv/eys7MTOTk5ou/TarUoLi62d7t0Cb6+vli3bh0yMzOhUChE9draWhw6dAg9PT0O6I6IaO457YdfDz74IF599VUAgFwux80334yXX34ZwPml+gBI3vIRGRmJ3t5eGAwGqFQqUd1gMExZFEHqweJkGzKZDEuXLkVERAR0Op0oTAcHB3H06FEkJSUhJSVFMoiJiFyF085g77//fhw6dAhvvPEGbrjhBphMJsv9lZNr4EoF6OTSfFLr5ALAjh07EBgYaHnFxsba6G9A0/H398c111yDtLQ0yOVT/wkKgoCqqiocPnwY586dc1CHRESXz2kDNikpCbm5ubjzzjuxf/9+DA0N4aabboIgCJantUgtzzc2NgYA0z7R5dFHH0V/f7/l1dTUZLu/BE1LLpcjKSkJGzZswIIFC0T1/v5+HD58GBUVFTCbzQ7okIjo8jhtwH7dpk2boNfrUVtbazk1PHmq+EJtbW0ICgqSnN0C52e9AQEBU17kOIGBgVi/fj1SU1MlZ7MVFRU4cuQI+vv7HdQhEdHsuEzATp7y7e/vR3R0NEJDQ1FQUCAap9PpkJGRYefu6HLI5XKkpKQgNzcXgYGBovq5c+dw6NAhVFdXczZLRC7D6QK2s7NTtM1oNOLNN9+Et7c3UlJSAAC33HIL9u/fP+UU75EjR1BbW4vNmzfbrV+aO2q1Grm5uUhOThY9S9ZsNqOsrAzHjh3D4OCggzokIrKe0y008Z3vfAcDAwNYs2YNoqOj0d7ejrfffhvV1dXYvXs3tm/fDgBoampCZmYm1Go1fvazn2FoaAi7du1CTEwM9Hr9tKeIv44LTTin3t5e6HQ6yau8FQoFli9fjqVLl/Kh7kTktJwuYP/7v/8be/fuxcmTJ9HT0wN/f39kZ2fjvvvuwze/+c0pYysqKrB9+3bk5+dDqVRi48aN2L17N8LDw61+Pwas8zKZTCgvL0dNTY1kPTQ0FBqNBn5+fnbujIjo0pwuYO2NAev8uru7odPpMDQ0JKp5eHggLS0Nixcv5myWiJwKA5YB6xImJiZQVlaGU6dOSdbDw8Oh0Wjg4+Nj586IiKQxYBmwLqWzsxM6nQ4jIyOimqenJzIyMrBw4ULOZonI4RiwDFiXYzQaUVpaijNnzkjWIyMjkZOTM+1iI0RE9sCAZcC6rLa2NhQUFEgui6lUKpGVlYXY2FjOZonIIRiwDFiXNj4+juLiYpw9e1ayHhMTg6ysLMsa1URE9sKAZcDOCy0tLSgsLLSsRX0hlUqF7OxsxMTEOKAzInJXDFgG7LxhMBhQVFQ07QMc4uLikJWVBaVSaefOiMgdMWAZsPNOU1MTCgsLLY83vJCXlxc0Go3ks4SJiOYSA5YBOy+NjY2hoKAAra2tkvVFixYhIyMDnp6edu6MiNwFA5YBO28JgoCzZ8+iuLgYRqNRVPfx8YFGo5nR0ppERNZiwDJg572RkREUFBSgvb1dsr548WKkpaVxNktEc4oBy4B1C4Ig4MyZMygtLcXExISo7uvrC61Wi9DQUAd0R0TzEQOWAetWhoeHodfrJZ87DACJiYlITU2Fh4eHnTsjovmGAcuAdTuCIODUqVMoKyuDyWQS1f39/aHVahEcHOyA7ohovmDAMmDd1uDgIHQ6HXp6ekQ1mUyGZcuW4YorroBCoXBAd0Tk6hiwDFi3ZjabUVtbi/LycpjNZlE9MDAQWq0WCxYscEB3ROTKGLAMWALQ398PvV6P3t5eUU0mkyElJQXJycmQy+UO6I6IXBEDlgFL/2Q2m1FdXY3KykrJ2eyCBQug1WoRGBjogO6IyNUwYBmw9DV9fX3Q6XTo6+sT1eRyOa644gosW7aMs1kiuigGLAOWJJhMJlRWVqK6uhpSPyLBwcHQaDT8N0NE02LAMmDpInp7e6HT6TAwMCCqKRQKLF++HEuXLuVD3YlIhAHLgKVLMJlMKC8vR01NjWQ9NDQUGo0Gfn5+du6MiJwZA5YBS1bq7u6GTqfD0NCQqObh4YG0tDQsXryYs1kiAsCAZcDSjExMTODkyZOoq6uTrIeHh0Oj0cDHx8fOnRGRs2HAMmBpFjo7O6HX6zE8PCyqeXp6IiMjAwsXLuRslsiNMWAZsDRLRqMRpaWlOHPmjGQ9MjISOTk58Pb2tnNnROQMGLAMWLpM7e3t0Ov1GB0dFdWUSiUyMzMRFxfH2SyRm5nVnfKff/45tm/fPu0DrNva2rB9+3Z8+eWXl9UckSuIiIhAXl4eFi5cKKqNj4/jxIkT+OKLLzA2Nmb/5ojIYWY1g73llltQVlY27YUewPnnamZmZuIvf/nLZTVoa5zB0lxqaWlBYWGhZJiqVCpkZ2cjJibGAZ0Rkb3Nagar1+uxevXqi45Zs2YNZ7DkdqKjo5GXl4fY2FhRzWAw4Pjx4/jyyy9hMBgc0B0R2dOsArazsxPR0dEXHRMREYHOzs5ZNUXkylQqFVauXImVK1dCqVSK6o2NjThw4ABaW1sd0B0R2cusAlatVqOxsfGiY86ePcuVbcitxcbG4vrrr5f8ZXRsbAz5+fnQ6/UYHx93QHdEZGuzCtgrr7wSH3zwAZqamiTrjY2N+Otf/4pVq1ZdVnNErs7LywurVq3CihUr4OnpKarX19fj4MGD6OjocEB3RGRLswrY7du3Y2RkBFdddRXefPNNtLW1ATh/9fAbb7yBq666CqOjo3jwwQfntFkiVySTyRAfH4+8vDxERESI6iMjI/jkk09QWFgIo9HogA6JyBZmfR/siy++iAcffNDyKC+ZTGb5s1wuxwsvvIB777137jq1EV5FTPYkCALq6+tRUlKCiYkJUd3X1xdarRahoaEO6I6I5tJlLTRRVlaGP/7xj9Dr9ejv74darYZWq8VPfvITpKamzmWfNsOAJUcYHh6GXq+f9kLAxMREpKamwsPDw86dEdFc4UpODFhyEEEQcOrUKZSVlcFkMonq/v7+0Gq1CA4OdkB3RHS5GLAMWHKwwcFB6PV6dHd3i2oymQzLli3DFVdcAYVC4YDuiGi2ZnSR0zPPPIPHHnvsohdijI+P47HHHsNvf/vby26OyB34+/tj3bp1SE9Ph1w+9UdSEARUV1fj8OHDOHfunIM6JKLZsDpgDx8+jF/+8pcIDg6WvN1gklKpREhICB5//HEcO3ZsTpokmu/kcjmWLVuG6667DkFBQaJ6f38/Dh8+jIqKCpjNZgd0SEQzZfUp4jvvvBN///vf0dLSApVKddGxBoMBMTExuOGGG/Dmm2/OSaO2wlPE5GzMZjOqq6tRWVkpGaYLFiyAVqtFYGCgA7ojImtZPYM9fvw4cnNzLxmuwPml4nJzc/H5559fVnNE7kgulyMlJQW5ublQq9Wi+rlz53Do0CFUVVVxNkvkxKwO2NbWViQkJFi940WLFlkWoCCimVOr1Vi/fj1SUlJEz5I1m804efIkjh49ioGBAQd1SEQXY3XAyuXyGa0yYzQaRRdsENHMKBQKpKamYv369ZIfYfT29uLQoUOoqamBm98QQOR0rE7AqKgolJeXW73j8vLySz5xh4isExQUhA0bNmDZsmWimslkQmlpKY4dO4ahoSEHdEdEUqwO2KuvvhpHjx5FQ0PDJcc2NDTg6NGjWLNmzeX0RkQXUCgUSE9Px7XXXiv5pKru7m4cPHgQp06d4myWyAlYHbD33nsvjEYjNm3aJHlD/KSenh5s3rwZExMT+Nd//dc5aZKIvhISEoLrrrsOS5cuFdUmJiZQVFSETz/9FMPDww7ojogmzWglp+3bt+OFF15ASEgIfvKTn+Caa65BTEwMAKClpQVHjhzBnj170NXVhe3bt+O5556zWeNzhbfpkCvr7OyEXq+XDFNPT0+kp6dj0aJFooukiMj2ZhSwgiDg8ccfx65duyRvDxAEAQqFAj//+c/xm9/8ZlY/1Hq9Hm+88QaOHTuGhoYGBAcH48orr8RvfvMbJCYmThlbVVWFBx54APn5+VAqldi4cSOef/75GT2JhAFLrs5oNKK0tBRnzpyRrEdGRiInJwfe3t527ozIvc1qLeLTp0/jtddew/Hjx9He3g4AiIiIwFVXXYW77roLixcvnnVDmzZtwueff47NmzcjLS0N7e3tePnllzE0NIQvv/zS8pSe5uZmZGZmIjAwENu2bcPQ0BCee+45xMXFQafTQalUWvV+DFiaL9rb26HX6zE6OiqqKZVKZGZmIi4ujrNZIjtxusX+jx8/jpycnCkBWVdXh+XLl2PTpk146623AAD33HMPXn/9dVRXVyMuLg7A+eUcN2zYgFdffRVbt2616v0YsDSfjI+Po6SkZNqLEaOjo5GdnQ0vLy/7NkbkhpwuYKeTnZ0NACgsLAQAhIeHY+3atXj33XenjFu2bBliY2Nx+PBhq/bLgKX5qKWlBYWFhRgbGxPVVCoVsrOzLddPEJFtuMRKEIIgoKOjAyEhIQDOHzw6OzuRk5MjGqvValFcXDztvgwGAwYGBqa8iOab6Oho5OXlITY2VlQzGAw4fvw4vvzySxgMBgd0R+QePKwd+I1vfGPGO5fJZPjb3/424+/7urfffhstLS14+umnAcCyBGNkZKRobGRkJHp7e2EwGCTXTd6xYweeeuqpy+6JyNmpVCqsXLkSMTExKCoqEoVpY2Oj5RfVqKgoB3VJNH9ZHbD/+Mc/ZrzzubiYorq6Gvfeey9WrlyJH/7whwBguYhDKkAnP1saHR2VrD/66KPYvn275euBgQHJ3/KJ5ovY2FiEhoaisLAQLS0tU2pjY2PIz8/HwoULkZGRYfXFgUR0aVYHbH19vS37kNTe3o6NGzciMDAQ+/btg0KhAADL7QZSp7cmP3Oa7pYElUpl1ROBiOYTLy8vrFq1Co2NjSgqKhKtK97Q0GCZzUZERDioS6L5xeqAjY+Pn/HOL+fznf7+ftxwww3o6+vDZ599NuUU1uSpYamn9bS1tSEoKIghSvQ1MpkM8fHxCA0NRUFBgeUWu0kjIyP49NNPsXjxYqSlpcHT09NBnRLNDza5yKmoqAj33nvvrD/XGRsbw0033YTa2lrs378fKSkpU+rR0dGWg8TX6XQ6ZGRkzOp9idyBj48Prr76auTk5MDDQ/w79unTp3Hw4EF0dnY6oDui+WPOAravrw8vv/wyMjMzodFo8Morr0jeInApJpMJ3/3ud/HFF1/gvffew8qVKyXH3XLLLdi/fz+ampos244cOYLa2lps3rx51n8PIncgk8mQkJCAvLw8hIWFierDw8P4+OOPUVxcjImJCQd0SOT6Lvs+2MOHD2Pv3r348MMPYTAYIAgCVq5ciR/96Ef47ne/C39//xnt7/7778eLL76Im266Cbfeequo/oMf/AAA0NTUhMzMTKjVavzsZz/D0NAQdu3ahZiYGOj1eqtPEfM+WHJ3giDg1KlTKCsrg8lkEtX9/f2h1WoRHBzsgO6IXNesArapqQmvvfYaXnvtNTQ2NkIQBERHR6OlpQV33XUX/vSnP826oXXr1uGTTz6Ztn5huxUVFdi+ffuUtYh3796N8PBwq9+PAUt03uDgIPR6veTTsmQyGZYtW4YrrrjCcrEhEV2c1QFrNBrx17/+FXv37sWRI0dgMpng6+uL73znO7jzzjtx7bXXwsPDA1u2bMGePXts3fecYcASfcVsNqOurg4nT56UfKBHQEAAtFotgoKCHNAdkWux+iriqKgo9Pb2QiaT4ZprrsGdd96Jm2++Gb6+vrbsj4jsSC6XY9myZYiMjIROp0Nvb++U+sDAAI4cOYLk5GQkJydzNkt0EVYHbE9PD+RyOR544AH8/Oc/n9Ej4YjItQQEBODaa69FdXU1Kisrp8xmBUFAZWUlWltbodVqoVarHdcokROz+iriu+66C97e3nj++ecRExODb37zm3jvvfcwPj5uy/6IyEHkcjlSUlKQm5srGaJ9fX04fPgwqqqqJE8nE7k7qwP2T3/6E9ra2vDqq68iKysL+/fvx2233Ybw8HD8y7/8C/Lz823ZJxE5iFqtxvr165GSkiJa/tRsNuPkyZM4evQoH5xB9DWzvk2nqqoK//mf/4m33noLXV1dlh+81atX480335zVyk+OwIuciKzX29sLnU4nGaYKhQKpqalITEzkQ92JMAf3wU5MTFiuLj506BDMZjPkcjnWrl2Lu+66C3fcccdc9WoTDFiimTGZTCgvL0dtbS2kDh8hISHQarXw8/NzQHdEzmNOH7je3NxsuT+2oaEBMplM8sZ1Z8KAJZqd7u5u6HQ6DA0NiWoKhQLp6elYvHgxZ7PktuY0YC909OhR/Nu//Rvef/99W+x+zjBgiWZvYmICJ0+eRF1dnWQ9LCwMGo2Gt/ORW7LZYv/vv/8+jh07ZovdE5GT8PDwQGZmJtatWycZop2dnThw4ADOnDkjeTqZaD6bsxlsX18f3nrrLezduxdlZWUQBIGniInciNFoRGlpKc6cOSNZj4yMRE5OzrTPaiaab+Z8sX8AWLt2LQYGBlBcXMyAJXIz7e3t0Ov1GB0dFdWUSiUyMzMRFxfHz2Zp3pvVKeKmpiY8/fTTWLRoEfLy8vCXv/wFCQkJePbZZ3H27FkcPXoUmZmZc90rEbmAiIgI5OXlYeHChaLa+Pg4Tpw4gePHj8/qcZZErsTqpRKlFvuPiIjA/fffjzvuuIMPOSciC6VSCa1Wi5iYGBQUFIjCtKWlBd3d3cjKykJsbKyDuiSyrRkv9u/j44PbbrsNd9xxB3JzcyGX2+Q6KSKaB6KiopCXl4fi4mI0NjZOqRkMBnzxxRdoaWlBZmam1c9wJnIVM1rsX61WY/fu3bj99tv5w0BEVlGpVLjyyisRHR2NoqIiy7UakxobG9HZ2YmcnBxERUU5qEuiuTejxf6NRiO2bNmCiIgI3H333Rd9MDoR0YViY2ORl5eH6OhoUW1sbAz5+fnQ6XR8gAjNG7Na7D8pKQl79+7Ftddei/j4eDz++OOorKy0ZZ9ENA94eXlh1apVWLFiBTw9PUX1hoYGHDhwAO3t7Q7ojmhuzegDVD8/P2zZsgVffPEFKioqcP/992NsbAw7duzA8uXLkZOTgxdffBEdHR226peIXJxMJkN8fDyuv/56REZGiuqjo6P49NNPUVBQAKPR6IAOiebGnC/2LwgCFAoF/P390dfXx/tgiWhagiCgvr4eJSUlmJiYENV9fX2h0WgQFhbmgO6ILo9NFvt//fXXUV9fz5WciMgqw8PD0Ov16OzslKwvXboUy5cvh4eH1ddlEjmczRb7P3LkCP70pz/h7bfftsXu5wwDlsg5CIKA06dPo7S0VPIXcz8/P2i1WoSEhDigO6KZs1nAugoGLJFzGRoagk6nQ3d3t6gmk8mQmJiI1NRUKBQKB3RHZD0GLAOWyOmYzWbU1dWhvLxccjYbEBAArVaLoKAgB3RHZB0GLAOWyGkNDAxAp9Oht7dXVJPJZEhOTkZycjJns+SUGLAMWCKnZjabUVNTg4qKCpjNZlFdrVZDq9VCrVbbvzmii2DAMmCJXEJfXx90Oh36+vpENblcjpSUFCQlJXF9dHIaDFgGLJHLMJvNqKysRFVVFaQOXUFBQdBqtfxZJqfAgGXAErmc3t5e6HQ6DAwMiGoKhQKpqalYunQpZ7PkUAxYBiyRSzKZTKioqEBNTY3kbDYkJARarRZ+fn4O6I6IAcuAJXJxPT090Ol0GBwcFNUUCgXS0tKwZMkSyGQyB3RH7owBy4AlcnkTExM4efIk6urqJOthYWHQaDTw9fW1c2fkzhiwDFiieaOrqws6nQ7Dw8OimoeHBzIyMrBo0SLOZskuGLAMWKJ5xWg0oqysDKdPn5asR0REQKPRwNvb286dkbthwDJgieal9vZ26PV6jI6Oimqenp7IyspCXFwcZ7NkMwxYBizRvDU+Po6SkhI0NDRI1qOjo5GdnQ0vLy/7NkZugQHLgCWa91pbW1FQUICxsTFRTaVSISsrC7GxsQ7ojOYzBiwDlsgtGAwGFBcXo7GxUbIeGxuLrKwsqFQqO3dG8xUDlgFL5Faam5tRWFgIg8Egqnl5eSEnJwdRUVEO6IzmGwYsA5bI7YyNjaGwsBAtLS2S9YULFyIjIwNKpdLOndF8woBlwBK5JUEQ0NjYiOLiYoyPj4vq3t7e0Gg0iIiIcEB3NB8wYBmwRG5tdHQUBQUFaGtrk6wnJCQgPT0dnp6edu6MXB0DlgFL5PYEQUB9fT1KSkowMTEhqvv6+kKj0SAsLMwB3ZGrYsAyYInon4aHh1FQUICOjg7J+tKlS7F8+XJ4eHjYuTNyRQxYBiwRXUAQBJw+fRplZWWSs1k/Pz9otVqEhIQ4oDtyJQxYBiwRSRgaGoJOp0N3d7eoJpPJkJiYiNTUVCgUCgd0R66AAcuAJaJpCIKA2tpalJeXw2QyieoBAQHQarUICgpyQHfk7BiwDFgiuoSBgQHodDr09vaKajKZDMnJyUhOTuZslqZgwDJgicgKZrMZNTU1qKiogNlsFtXVajW0Wi3UarX9myOnJHd0A183NDSEJ598Etdffz2CgoIgk8nw+uuvS46tqqrC9ddfDz8/PwQFBeGOO+5AV1eXfRsmIrcgl8uRnJyMDRs2SIZoX18fDh8+jMrKSskAJvfjdAHb3d2Np59+GlVVVUhPT592XHNzM9asWYNTp07h2WefxUMPPYS//e1v2LBhg+SqLEREcyEwMBC5ublISUkRPUvWbDajvLwcR48excDAgIM6JGfhdDdzRUZGoq2tDRERESgoKIBGo5Ec9+yzz2J4eBiFhYWIi4sDAGi1WmzYsAGvv/46tm7das+2iciNyOVypKamIjo6GidOnBCFaW9vLw4ePIjly5dj6dKlkMudbi5DduB0/9VVKpVVa3++//77uPHGGy3hCgC5ublITEzEu+++a8sWiYgAAAsWLMCGDRuQlJQkOZstLS3Fxx9/jMHBQQd1SI7kdAFrjZaWFnR2diInJ0dU02q1KC4unvZ7DQYDBgYGpryIiGZLoVAgLS0N1157Lfz9/UX17u5uHDx4EHV1dXDza0rdjksG7OSi3JGRkaJaZGQkent7JZ/1CAA7duxAYGCg5RUbG2vTXonIPQQHB2PDhg1ITEwU1UwmE4qLi/HJJ59geHjYAd2RI7hkwI6OjgI4fzr567y8vKaM+bpHH30U/f39lldTU5PtGiUit+Lh4YGMjAxcc8018PX1FdU7Oztx4MABnDlzhrNZN+CSAevt7Q0AkrPUsbGxKWO+TqVSISAgYMqLiGguhYaG4rrrrsPixYtFtYmJCRQUFOCzzz7DyMiIA7oje3HJgJ08NSz1/Ma2tjYEBQVJzm6JiOzF09MT2dnZWLt2LXx8fET19vZ2HDhwAA0NDZzNzlMuGbDR0dEIDQ1FQUGBqKbT6ZCRkWH/poiIJISHh+O6667DokWLRDWj0QidTofPP//ccvaN5g+XDFgAuOWWW7B///4pn6EeOXIEtbW12Lx5swM7IyKaSqlUQqPRYPXq1ZbrRC7U2tqKf/zjH7wmZJ5xyrWIX375ZfT19aG1tRWvvPIKbr75ZmRmZgIA7rvvPgQGBqKpqQmZmZlQq9X42c9+hqGhIezatQsxMTHQ6/VWnyLmWsREZE8GgwHFxcVobGyUrMfGxiIrK4sfc80DThmwCxcuxNmzZyVr9fX1WLhwIQCgoqIC27dvR35+PpRKJTZu3Ijdu3cjPDzc6vdiwBKRIzQ3N6OwsFDyYk0vLy9kZ2cjOjraAZ3RXHHKgLUnBiwROcrY2BiKiorQ3NwsWY+Pj0dmZiaUSqWdO6O5wIBlwBKRAwmCgKamJhQVFUk+qMTb2xsajcaqJWTJuTBgGbBE5ARGR0dRUFAgefshACQkJCA9PR2enp527oxmiwHLgCUiJyEIAhoaGlBSUgKj0Siq+/r6QqPRICwszAHd0UwxYBmwRORkhoeHUVBQgI6ODsn6kiVLkJaWBg8Pp3viKF2AAcuAJSInJAgCTp8+jbKyMkxMTIjqfn5+0Gq1CAkJcUB3ZA0GLAOWiJzY0NAQ9Ho9urq6JOvLli1DamoqFAqFnTujS2HAMmCJyMkJgoC6ujqcPHkSJpNJVA8ICIBWq0VQUJADuqPpMGAZsETkIgYGBqDX69HT0yOqyWQyJCUlISUlhbNZJ8GAZcASkQsxm82oqalBRUUFzGazqK5Wq6HVaqFWq+3fHE3BgGXAEpEL6u/vx4kTJ9DX1yeqyeVypKSkICkpCXK5yz7TxeUxYBmwROSizGYzqqqqUFlZKflM2aCgIGi1Wh7bHIQBy4AlIhd37tw56HQ69Pf3i2pyuRypqalITEzkbNbOGLAMWCKaB0wmEyoqKlBTUyM5mw0JCYFGo4G/v78DunNPDFgGLBHNIz09PdDpdBgcHBTVFAoF0tLSsGTJEshkMgd0514YsAxYIppnJiYmUF5ejtraWsl6WFgYNBoNfH197dyZe2HAMmCJaJ7q6uqCTqfD8PCwqObh4YH09HQkJCRwNmsjDFgGLBHNY0ajEWVlZTh9+rRkPSIiAjk5OfDx8bFzZ/MfA5YBS0RuoKOjA3q9HiMjI6Kap6cnMjMzER8fz9nsHGLAMmCJyE2Mj4+jtLQU9fX1kvWoqCjk5OTAy8vLzp3NTwxYBiwRuZnW1lYUFBRgbGxMVFMqlcjOzkZsbKwDOptfGLAMWCJyQwaDAcXFxWhsbJSsx8bGIisrCyqVys6dzR8MWAYsEbmx5uZmFBYWwmAwiGpeXl7Izs5GdHS0AzpzfQxYBiwRubmxsTEUFRWhublZsh4fH4/MzEwolUo7d+baGLAMWCIiCIKApqYmFBUVYXx8XFT39vZGTk4OIiMjHdCda2LAMmCJiCxGR0dRUFCAtrY2yXpCQgLS09Ph6elp585cDwOWAUtENIUgCGhoaEBJSQmMRqOo7uPjA61Wi7CwMAd05zoYsAxYIiJJIyMj0Ov16OjokKwvWbIEaWlp8PDwsHNnroEBy4AlIpqWIAg4c+YMSktLMTExIar7+flBq9UiJCTEAd05NwYsA5aI6JKGhoag1+vR1dUlWV+2bBlSU1OhUCjs3JnzYsAyYImIrCIIAurq6nDy5EmYTCZRPSAgAFqtFkFBQQ7ozvkwYBmwREQzMjAwAL1ej56eHlFNJpMhKSkJKSkpbj+bZcAyYImIZsxsNqOmpgYVFRUwm82iulqthlarhVqttn9zToIBy4AlIpq1/v5+6HQ6nDt3TlSTy+VISUlBUlIS5HK5A7pzLAYsA5aI6LKYzWZUVVWhsrISUpGyYMECaLVaBAYGOqA7x2HAMmCJiObEuXPnoNPp0N/fL6rJ5XKkpqYiMTHRbWazDFgGLBHRnDGZTKioqEBNTY3kbDY4OBharRb+/v4O6M6+GLAMWCKiOdfT0wOdTofBwUFRTaFQIC0tDUuWLIFMJnNAd/bBgGXAEhHZxMTEBMrLy1FbWytZDwsLg0ajga+vr507sw8GLAOWiMimurq6oNPpMDw8LKp5eHggPT0dCQkJ8242y4BlwBIR2ZzRaERZWRlOnz4tWY+IiEBOTg58fHzs3JntMGAZsEREdtPR0QG9Xo+RkRFRzdPTE5mZmYiPj58Xs1kGLAOWiMiujEYjSkpKUF9fL1mPiopCdnY2vL297dzZ3GLAMmCJiByira0Ner0eY2NjoppSqURWVhbi4uIc0NncYMAyYImIHMZgMKCkpARnz56VrMfExCA7OxsqlcrOnV0+91hOg4iInJJKpcKKFSuwatUqyRBtbm7GP/7xD7S0tJzfkJ8/szeY6fg5xIAlIiKHi4mJQV5eHmJiYkQ1g8GAzz//HM1btgBXXw3s3GndTnfuPD/+V7+a22atxIAlIiKn4OXlhZUrV+LKK6+EUqmcUguprkbM3r3nv3jkkUuH7M6d58cBwFNPOWQm69IBazAY8PDDDyMqKgre3t5YsWIFDh065Oi2iIholmQyGeLi4pCXl4eoqCjL9u6kJJTdfvtXAx95BKZnn5XeyYXhCgC//S2werWNOp6eSwfsXXfdheeffx7f//738eKLL0KhUOAb3/gG8h14zp2IiC6ft7c3rrrqKmi1Wnh6egIAqr/1rSkhq3j8cQw+8cTUb5QK14cftkfLIi57FbFOp8OKFSuwa9cuPPTQQwCAsbExpKamIiwsDMePH7dqP7yKmIjIuY2MjECv16OjowMAkPThh0h75x1LveW++xD+/PPw2L3bacIVcOEZ7L59+6BQKLB161bLNi8vL/z4xz/GF198gaamJgd2R0REc8XHxwdr1qxBdnY2PDw8RDPZ6JdegjkoyKnCFQA8HPrul6G4uBiJiYmiWadWqwUAlJSUIDY2VvR9BoMBBoPB8vXAwIBtGyUiossmk8mwePFihIeHQ6/Xo/pb3wIAy0xWecFj8czPPgu5g8MVcOEZbFtbGyIjI0XbJ7e1trZKft+OHTsQGBhoeUmFMBEROSc/Pz+sW7cOGRkZqLv5Zhj8/KbUjf7+Dp+5TnLZgB0dHZW8KdnLy8tSl/Loo4+iv7/f8uKpZCIi1yKTyZCYmIgbKyqgGhqaUvMcHIR81y4HdTaVy54i9vb2nnKqd9LkmpbTLRKtUqlccsktIiK6wM6dUD35pOVLg5/fV2E7+Vmsg2eyLjuDjYyMRFtbm2j75LYL758iIqJ5ROJWnPHWVgg7dny1zZrFKGzMZQM2IyMDtbW1oouUTpw4YakTEdE8M819rv7+/pA98sj5ryc5OGRdNmA3bdoEk8mEPXv2WLYZDAa89tprWLFiBS9eIiKab6xZROLhh50mZF12oQkAuPXWW/HBBx/ggQcewJIlS/DGG29Ap9PhyJEjWLNmjVX74EITREQuID///ML9ky51n+vXw/izz+y+XKLLzmAB4M0338T999+PP//5z9i2bRuMRiP2799vdbgSEZGLWL0amLyoyZpFJC6cyT75pEPWInbpGexc4AyWiMiF5OfPLCxnOn4OMWAZsEREZAMufYqYiIjIWTFgiYiIbIABS0REZAMMWCIiIhtgwBIREdkAA5aIiMgGGLBEREQ2wIAlIiKyAQYsERGRDTBgiYiIbIABS0REZAMMWCIiIhtgwBIREdkAA5aIiMgGGLBEREQ2wIAlIiKyAQYsERGRDTBgiYiIbIABS0REZAMMWCIiIhvwcHQDjiYIAgBgYGDAwZ0QEZEz8Pf3h0wmu+z9uH3ADg4OAgBiY2Md3AkRETmD/v5+BAQEXPZ+ZMLkFM5Nmc1mtLa2XvZvLAMDA4iNjUVTU9Oc/IchIiLrzPXxlzPYOSKXyxETEzNn+wsICGDAEhE5gLMdf3mRExERkQ0wYImIiGyAATtHVCoVnnzySahUKke3QkTkVpz1+Ov2FzkRERHZAmewRERENsCAJSIisgEGLBERkQ0wYImIiGyAAUtERGQDbhOwr7/+OmQyGWQyGfLz80V1QRAQGxsLmUyGG2+8cUb7bmlpwa233gq1Wo2AgAB861vfwpkzZyTH7t27F8nJyfDy8sLSpUvx0ksvXfY+iYicma2OvzU1NXjggQewatUqeHl5QSaToaGhYdrx//u//4usrCx4eXkhLi4OTz75JCYmJkTj+vr6sHXrVoSGhsLX1xfXXHMNioqKrO5rktsE7CQvLy+88847ou2ffPIJmpubZ3wf1dDQEK655hp88skneOyxx/DUU0+huLgYa9euRU9Pz5Sxr776KrZs2YIrrrgCL730ElauXIlt27Zh586ds94nEZGrmOvj7xdffIE//OEPGBwcRHJy8kXHfvTRR/j2t78NtVqNl156Cd/+9rfxm9/8Bvfdd9+UcWazGRs3bsQ777yDn/70p/jd736Hzs5OrFu3DnV1dTPqD4KbeO211wQAws033yyEhIQIRqNxSv3uu+8WsrOzhfj4eGHjxo1W73fnzp0CAEGn01m2VVVVCQqFQnj00Uct20ZGRoTg4GDRvr///e8Lvr6+Qm9v74z3SUTkCmx1/O3p6REGBgYEQRCEXbt2CQCE+vp6ybEpKSlCenr6lPd+/PHHBZlMJlRVVVm2/eUvfxEACO+9955lW2dnp6BWq4Xvfe97VvcmCILgdjPY733ve+jp6cGhQ4cs28bHx7Fv3z7cfvvtM97fvn37oNFooNFoLNuSkpKwfv16vPvuu5Ztx44dQ09PD+65554p33/vvfdieHgYf/vb32a8TyIiVzLXx9+goCD4+/tfclxlZSUqKyuxdetWeHh89Yybe+65B4IgYN++fZZt+/btQ3h4OG6++WbLttDQUNx666348MMPYTAYrO7P7QJ24cKFWLlyJf7rv/7Lsu2jjz5Cf38/brvtthnty2w2o6ysDDk5OaKaVqvF6dOnLc+bLS4uBgDR2OzsbMjlckt9JvskInIlc3n8nYnpjr9RUVGIiYmx1CfHZmVlQS6fGo9arRYjIyOora21+n3dLmAB4Pbbb8df//pXjI6OAgDefvttrF27FlFRUTPaT29vLwwGAyIjI0W1yW2tra0AgLa2NigUCoSFhU0Zp1QqERwcbBk3k30SEbmauTr+zkRbWxsATHtcvfCY2tbWNmfHX7cM2FtvvRWjo6PYv38/BgcHsX///lmdnpj8ByL1wbyXl9eUMaOjo1AqlZL78fLymjLO2n0SEbmauTr+zsSljqsXHlNHR0fn7Pjrlg9cDw0NRW5uLt555x2MjIzAZDJh06ZNM96Pt7c3AEiekx8bG5syxtvbG+Pj45L7GRsbmzLO2n0SEbmauTr+zsSljqsXHlO9vb3n7PjrlgELnD9Ncffdd6O9vR033HAD1Gr1jPcRFBQElUplOf1wocltk6c9IiMjYTKZ0NnZOeU08fj4OHp6eizjZrJPIiJXNBfH35mYPL3b1taG2NjYKbW2tjZotdopY+fq+OuWp4gB4Dvf+Q7kcjm+/PLLWZ+ekMvlWL58OQoKCkS1EydOICEhwXKFW0ZGBgCIxhYUFMBsNlvqM9knEZErmovj70xMd/xtbW1Fc3OzpT45tqioCGazecrYEydOwMfHB4mJiVa/r9sGrJ+fH1555RX86le/wk033TTr/WzatAl6vX7Kf7iamhocPXoUmzdvtmy79tprERQUhFdeeWXK97/yyivw8fHBxo0bZ7xPIiJXNFfHX2tdccUVSEpKwp49e2AymSzbX3nlFchksimnqDdt2oSOjg78z//8j2Vbd3c33nvvPdx0000zWgzDbU8RA8APf/jDy97HPffcg//4j//Axo0b8dBDD8HT0xPPP/88wsPD8eCDD1rGeXt749e//jXuvfdebN68GXl5efjss8/w1ltv4ZlnnkFQUNCM90lE5Krm4vjb399vWW72888/BwC8/PLLUKvVUKvV+OlPf2oZu2vXLnzzm9/Eddddh9tuuw3l5eV4+eWXsWXLlimrQG3atAlXXnklfvSjH6GyshIhISH493//d5hMJjz11FMza3BGy1K4sMmVRPR6/UXHzXQlEUEQhKamJmHTpk1CQECA4OfnJ9x4441CXV2d5Ng9e/YIy5YtE5RKpbB48WLh97//vWA2my9rn0REzsxWx9/6+noBgOQrPj5eNP6DDz4QMjIyBJVKJcTExAhPPPGEMD4+LhrX29sr/PjHPxaCg4MFHx8fYe3atZfsXYpMEARh9r8/EBERkRS3/QyWiIjIltz6M9iL6e3tnfa+VQBQKBQIDQ21Y0dERO5hvhx/eYp4GuvWrcMnn3wybT0+Pv6izx0kIqLZmS/HXwbsNAoLC3Hu3Llp697e3rjqqqvs2BERkXuYL8dfBiwREZEN8CInIiIiG2DAEhER2QADloiIyAYYsERERDbAgCUiIrIBBiwREZENMGCJiIhs4P8D+DJRuGCCuSoAAAAASUVORK5CYII=",
+      "text/plain": [
+       "<Figure size 500x400 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "petab_select.plot.line_selected(\n",
+    "    models=models,\n",
+    "    criterion=petab_select.Criterion.AICC,\n",
+    "    labels=labels,\n",
+    ");"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8e5243e0",
+   "metadata": {},
+   "source": [
+    "## Selection history trajectory\n",
+    "\n",
+    "This shows the relationship between models across iterations. For example, `M_000` was the predecessor model to `M_001`, `M_010`, and `M_100`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "862a78ef",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA7YAAAOwCAYAAAAKo+iFAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAACtV0lEQVR4nOzdd3iUVf7+8XvSKCEJBGbonSBFpKOQUCV0pAqCFIGALMUuinVBUbEtAopkqNKkg0gNJUCigFQpIr0rSA2hpc3vj/0mP1lQCSQ5U96v69rLZebhmRvWJdz5nOcci8PhcAgAAAAAABflZToAAAAAAAAPgmILAAAAAHBpFFsAAAAAgEuj2AIAAAAAXBrFFgAAAADg0ii2AAAAAACXRrEFAAAAALg0ii0AAAAAwKX53MtFKSkpOnPmjAICAmSxWDI7EwAAAADAwzkcDl29elWFChWSl9ffz2TvqdieOXNGRYsWzZBwAAAAAADcq5MnT6pIkSJ/e809FduAgIC0GwYGBj54MgAAAAAA/kZcXJyKFi2a1kf/zj0V29Tlx4GBgRRbAAAAAECWuZfHYdk8CgAAAADg0ii2AAAAAACXRrEFAAAAALg0ii0AAAAAwKVRbAEAAAAALo1iCwAAAABwaRRbAAAAAIBLo9gCAAAAAFwaxRYAAAAA4NIotgAAAAAAl0axBQAAAAC4NIotAAAAAMClUWwBAAAAAC6NYgsAAAAAcGkUWwAAAACAS6PYAgAAAABcGsUWAAAAAODSKLYAAAAAAJdGsQUAAAAAuDSKLQAAAADApVFsAQAAAAAujWILAAAAAHBpFFsAAAAAgEuj2AIAAAAAXBrFFgAAAADg0ii2AAAAAACXRrEFAAAAALg0ii0AAAAAwKVRbAEAAAAALo1iCwAAAABwaRRbAAAAAIBLo9gCAAAAAFwaxRYAAAAA4NIotgAAAAAAl0axBQAAAAC4NIotAAAAAMClUWwBAAAAAC6NYgsAAAAAcGkUWwAAAACAS6PYAgAAAABcGsUWAAAAAODSfEwHAACYcfDgQV29etV0DKcSEBCgkJAQ0zEAAEA6UWwBwAMdPHhQZcuWNR3DKR04cIByCwCAi6HYAoAHSp3UDho0SIULFzacxjmcPn1aY8eOZYoNAIALotgCgAcrXLiwSpUqZToGAADAA2HzKAAAAACAS6PYAgAAAABcGsUWAAAAAODSKLYAAAAAAJdGsQUAAAAAuDSKLQAAAADApVFsAQAAAAAujWILAAAAAHBpFFsAAAAAgEuj2AIAAAAAXBrFFgAAAADg0ii2AAAAAACXRrEFAAAAALg0ii0AAAAAwKVRbAEAAAAALo1iCwAAAABwaRRbAAAAAIBLo9gCAAAAAFwaxRYAAAAA4NIotgAAAAAAl0axBQAAAAC4NIotAAAAAMClUWwBAAAAAC6NYgsAAAAAcGk+pgMAAJxXdHS0xo0bJ0kaNmyYypUrd9v7DodDAwcO1IULF1StWjW99tpr93zvixcvaurUqfr555/lcDhUsWJF9ejRQ/nz57/tulWrVmnPnj06dOiQLly4oPr162vAgAF3vee1a9c0Y8YMbdmyRQkJCSpdurS6d++uUqVKpfNXDgAAXAkTWwDAP/L19VVMTMwdr+/bt08XLlyQr69vuu538+ZNDR8+XL/88ovatm2rJ598UkePHtWwYcN09erV26797rvvtHfvXhUtWlTe3t5/ec+UlBR99NFHiomJUdOmTfX0008rLi5Ow4cP12+//ZaufAAAwLVQbAEA/6hq1aratGmTkpOTb3s9NjZWpUqVUu7cudN1v5UrV+q3337TkCFD1KZNG7Vs2VJvvvmmLl26pO+///62a999911NmDBBQ4cOlY/PXy802rx5sw4cOKABAwboySefVNOmTfXuu+/Ky8tLc+fOTVc+AADgWii2AIB/FBoaqvj4eP38889pryUlJWnTpk0KDQ1N9/02b96s0qVLq0yZMmmvFS5cWA8//LB+/PHH2661Wq2yWCz/eM9NmzYpKChItWrVSnstMDBQjz32mLZu3arExMR05wQAAK6BYgsA+EdWq1UhISGKjY1Ne23Hjh26fv266tSpk657paSk6MSJE3d97rVMmTI6e/asbty4ke6Mx44dU8mSJeXldfuXtjJlyujWrVssRwYAwI1RbAEA9yQsLExbt25VQkKCJCkmJkYVKlRQcHBwuu4THx+vxMRE5cmT5473Upc0X7p0Kd35Ll26dNd7pr528eLFdN8TAAC4BootAOCe1K5dWwkJCdq2bZtu3Lih7du339cy5NRifLfnZf38/G67Jr33vds9Uze2YikyAADui+N+AAD3JDAwUJUqVVJsbKwSEhKUkpKixx57LN33SS2vSUlJd7yXWmhTr0nvfe92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zZOQkKApU6aoe/fuyp49u9EscD5eXl5q2bKlVq9erV27dqlJkyZ69913VaRIEQ0cOFAHDhwwHRGAh6HYugibzabLly8rISHBdBQAQAabP3++WrZsqXr16mnVqlXKkyeP6Uj67rvvdO7cOfXt29d0FDi5Rx55RBMnTtSJEyc0ZMgQzZs3Tw899JBatWqlNWvW8BwugCxBsXURVqtVknT+/HnDSQAAGWnChAnq1KmTOnTooEWLFilnzpymI0mS7Ha7ateurYcffth0FLgIm82md999V8ePH9fkyZN18uRJNW7cWJUrV9akSZN08+ZN0xEBuDGKrYuw2WySxHJkAHAjH3/8sfr27av+/ftr+vTpTnNO7LFjxxQVFcW0Fvcle/bseuaZZ7Rz506tWbNGJUqUUEREhIoXL65///vfOnv2rOmIANwQxdZFpBZbNpACANfncDj02muv6bXXXtPbb7+tsWPHysvLeb4kT5w4UQEBAerUqZPpKHBhFotFjRo10nfffadff/1VTz75pD755BMVK1ZMvXr10q5du0xHBOBGnOerKP5W6lJkJrYA4NqSk5PVr18/ffzxxxo1apSGDx8ui8ViOlaapKQkTZo0SV27dpW/v7/pOHATISEhGjt2rE6dOqX3339fa9asUZUqVdSoUSMtWbJEKSkppiMCcHEUWxeRM2dO+fv7U2wBwIXdunVLnTp10uTJkzV16lQ9//zzpiPdYdmyZTpz5gxn1yJT5MmTR6+++qqOHDmi2bNn68aNG3riiSf00EMPaezYsYqPjzcdEYCLoti6EM6yBQDXdfXqVbVs2VJLly7VggUL1KNHD9OR7sput6t69eqqWrWq6ShwYz4+PurUqZN+/PFH/fjjj6pevbpeeOEFFS1aVEOGDNGJEydMRwTgYii2LoSzbAHANV24cEGNGzfWTz/9pJUrV+qJJ54wHemuTp06pWXLlrFpFLLUY489pm+//VZHjhxR3759FRkZqVKlSqlz587atGmT6XgAXATF1oVQbAHA9Zw6dUp169bV0aNHtW7dOtWvX990pL80efJkZc+eXV26dDEdBR6oWLFi+vjjj3Xq1Cl98cUX2rFjh2rXrq3HHntMs2fPVlJSkumIAJwYxdaFsBQZAFzLgQMHFBoaqmvXrikmJkbVqlUzHekvJScna8KECXrqqacUGBhoOg48WK5cuTRw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jh3aunUrm0YBmaxEiRL69NNPderUKX322WfavHmzHn30UYWGhmrevHlKSkoyHRFwSxRbF5R6li0A4MHExcWpefPmWrlypRYvXqyuXbuajpRp7Ha7ChYsqJYtW5qOAniEgIAAPffcczpw4IAWLVokX19fPfnkkypTpow+++wzXblyxXREwK1QbF0QE1sAeHB//PGHGjVqpB07digqKkotWrQwHSnTXLt2TTNmzFDv3r3l4+NjOg7gUby9vdWmTRtFR0dr27Ztql+/voYOHaoiRYroueee0+HDh01HBNwCxdYFUWwB4MGcOHFCdevW1alTp7R+/XqFhYWZjpSp5s6dq7i4OPXp08d0FMCjVatWTVOnTtXx48f1wgsvaNasWQoJCVHbtm21fv16ORwO0xEBl0WxdUEsRQaA+7d//36Fhobq1q1biomJUeXKlU1HynSRkZEKDw9XyZIlTUcBIKlgwYJ67733dOLECUVGRurQoUNq0KCBqlevrm+++Ua3bt0yHRFwORRbF5Q6seW7egCQPlu3blXdunUVFBSk2NhYlSlTxnSkTLd37179+OOP6tevn+koAP5Hjhw5FBERod27d2vlypUqUKCAevbsqRIlSui9995jkAGkA8XWBdlsNiUmJrLpAACkw7p169SwYUOVKVNGGzZsUKFChUxHyhJ2u11Wq1VPPPGE6SgA/oLFYlGTJk20bNky7du3T23atNGHH36ookWLKiIiQnv27DEdEXB6FFsXZLVaJYnv4gHAPVq0aJGaN2+u2rVrKyoqSsHBwaYjZYmbN29q2rRpeuaZZ+Tn52c6DoB7UL58eX399dc6efKk3n33XS1fvlyVKlVSkyZNtHz5cqWkpJiOCDgliq0LstlsksQGUgBwD6ZMmaIOHTroiSee0JIlS5QrVy7TkbLMggULdPHiRUVERJiOAiCd8ubNq6FDh+ro0aOaMWOGLl26pBYtWqhixYr6+uuvdf36ddMRAadCsXVBqcWWiS0A/L3PP/9cvXr1UkREhGbNmqVs2bKZjpSlIiMjVb9+fZUtW9Z0FAD3yc/PT127dtWWLVu0ceNGVaxYUQMHDlTRokU1dOhQnT592nREwClQbF1QcHCwvLy8mNgCwF9wOBx688039fLLL2vo0KH6+uuv5e3tbTpWljpw4IDWr1/PplGAm7BYLAoLC9O8efN06NAh9ezZU19++aVKlCihp59+Wj/99JPpiIBRFFsX5OXlpXz58lFsAeAukpOT9a9//UsffPCBPv30U33wwQeyWCymY2W5CRMmKDg4WO3btzcdBUAGK1mypD7//HOdOnVKn376qX788UfVqlVLYWFhmj9/vpKTk01HBLIcxdZFcZYtANwpISFBXbt2ld1u16RJk/Tyyy+bjmREQkKCpkyZou7duyt79uym4wDIJIGBgXr++ed18OBBLViwQN7e3urYsaPKlCmjzz//nBM04FEoti4q9SxbAMB/Xbt2Ta1bt9aiRYs0b9489erVy3QkYxYvXqw//vhDffv2NR0FQBbw9vZWu3bttH79em3dulVhYWF6/fXXVaRIET3//PM6fPiw6YhApqPYuiiKLQD8fxcvXlTjxo31ww8/aMWKFWrXrp3pSEbZ7XbVqVNHFStWNB0FQBarXr26pk2bpmPHjun555/XjBkzFBISonbt2mnDhg1yOBymIwKZgmLroliKDAD/debMGdWvX18HDx7UunXr1LBhQ9ORjDp69KiioqKY1gIerlChQnr//fd14sQJjR8/XgcOHFD9+vXTim9CQoLpiECGoti6KCa2ACAdOnRIoaGhunz5smJiYlSjRg3TkYybOHGiAgMD9eSTT5qOAsAJ5MyZU3379tWePXu0YsUK2Ww29ejRQ8WLF9f777+v8+fPm44IZAiKrYuy2Ww6f/68UlJSTEcBACN27dqlsLAw+fn5KTY2VuXKlTMdybikpCRNmjRJTz/9tPz9/U3HAeBELBaLmjZtqhUrVmjv3r164oknNGLECBUtWlT9+vXT3r17TUcEHgjF1kVZrVYlJyfr0qVLpqMAQJaLiYlR/fr1VbhwYcXExKhYsWKmIzmFpUuX6rfffuPsWgB/q0KFCho/frxOnjypt99+W99//70efvjhtOLLc7hwRRRbF2Wz2SSJ5cgAPM6yZcvUpEkTVa1aVevWrZPVajUdyWnY7XbVqFFDVapUMR0FgAvIly+f3njjDR07dkzTpk3T+fPn1bx5c1WsWFHjx4/X9evXTUcE7hnF1kWlFls2kALgSWbOnKk2bdooPDxcy5cvV2BgoOlITuPkyZNavnw5m0YBSDc/Pz9169ZNW7du1YYNG1SuXDn961//UtGiRfXGG2/o9OnTpiMC/8jHdADcn9QJBRNbAJ7iyy+/1ODBg9WjRw9NmDBBPj58CfuzSZMmKUeOHOrSpYvpKABclMViUd26dVW3bl0dOXJEY8aM0dixY/XJJ5+oc+fOevHFF1W9enXTMe/JwYMHdfXqVdMxnEpAQIBCQkJMx8g0Fsc9LKKPi4tTUFCQrly5wnfHnYTD4ZCfn5+++OILDRgwwHQcAMg0DodD7733nt599129+OKL+vTTT+XlxYKjP0tOTlbJkiXVpEkTTZgwwXQcAG4kLi5OkyZN0ujRo3X06FGFhYXpxRdfVJs2beTt7W063l0dPHhQZcuWNR3DKR04cMClym16eijf7nZRFotFNpuNpcgA3FpKSopefPFFjR49WiNGjNDQoUNlsVhMx3I6q1at0smTJ9k0CkCGCwwM1AsvvKDBgwdr8eLF+s9//qMOHTqoRIkSeu6559SnTx+nG3ylTmoHDRqkwoULG07jHE6fPq2xY8e69RSbYuvCrFYrS5EBuK3ExET17t1bM2bM0Lhx49S/f3/TkZyW3W7XI488opo1a5qOAsBNeXt7q3379mrfvr22bt2qUaNGaciQIXr33XfVp08fDR48WKVKlTId8zaFCxd2ukzIPKzlcmE2m41iC8At3bhxQx06dNDs2bM1a9YsSu3f+O2337RkyRL17duXaTaALFGjRg1Nnz5dx44d0+DBg/XNN98oJCRE7du318aNGzkuCEZQbF2Y1WplKTIAt3PlyhU1bdpUa9as0ZIlS9S5c2fTkZzalClT5OPjo6efftp0FAAepnDhwhoxYoROnjypr776Sr/88ovq1aunmjVravr06UpISDAdER6EYuvCmNgCcDdnz55VgwYNtHv3bkVFRalp06amIzm1lJQUTZgwQZ06dVKePHlMxwHgoXLmzKlnn31We/fu1fLly5U3b151795dJUqU0IgRI3T+/HnTEe9bdHS0Onfu7JR/5x42bJiGDRtmOobToNi6MIotAHdy7Ngx1a1bV2fPntWGDRtUp04d05Gc3rp163TkyBHOrgXgFLy8vNSsWTOtXLlSe/bsUatWrfT++++raNGievbZZ7Vv3z7TESVJH3/8sbp3764bN2785TWjR49W165d073Z0sKFC/XTTz89aETcB4qtC7Narbp48aKSkpJMRwGAB7Jv3z6FhYUpOTlZMTExqlSpkulILsFut6t8+fIKDQ01HQUAblOxYkVFRkbqxIkTevPN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769x48bpm2++UXx8vH755RelpaWpZ8+eOnr0qNXRAcB2KLYAAADXWePGjfXUU09p69at2rNnjx5//HGtW7dOd9xxh4YMGSI3NzcVFxdr37596t27N8uSAaCMKLYAAADlqE2bNvrb3/6m1NRUffPNNyosLCw5KujcuXPatWuXevTooZycHIuTAoB9UGwBAAAs4Obmpl9++UVFRUWlxl0ul3766SeFhYVZlAwA7MfD6gAAAABV1dq1a0v+7O7uLofDoaCgIHl6eioiIsLCZABgLxRbAAAAi7zyyisaOXKkmjdvrsDAQHl6elodCQBsiWILAABgET8/P/n5+VkdAwBsj+/YAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1jvsBAACoQtatW6cPP/xQkvTyyy+rTZs2pa67XC49+uijOnbsmMLCwjRlypTLum9GRobi4+OVmpqqAwcOqLCwUO+9995FjzPatGmTtm7dqtTUVGVlZSk0NFQvvfTSRe9bWFiouLg4ffvtt8rNzVWzZs00YsQItWvXrow/OYDKjCe2AAAAVZCnp6c2btx4wfju3bt17NgxeXp6lul+ycnJWrFihfLz8xUYGPi7c+Pj47VlyxY1aNBAtWvX/t25H3zwgb7++mtFRERo7Nixcnd317Rp07R3794y5QNQuVFsAQAAqqAOHTooMTFR586dKzWekJCg4OBg1a1bt0z369ixo2JiYjR9+nRFRET87txHH31UMTExevHFF1WvXr1LzktNTdWmTZsUGRmpUaNGqU+fPnrhhRfUsGFDmaZZpnwAKjeKLQAAQBXUvXt35ebmaseOHSVjRUVFSkxMVPfu3ct8vzp16qhmzZqXNbdhw4Zyd//jv4YmJibK3d1dd9xxR8mYl5eXevXqpeTkZP36669lzgmgcqLYAgAAVEGNGjVSSEiIEhISSsa2bdumvLw8devWzcJk/+fgwYMKCAhQrVq1So23atVKknTo0CErYgGogCi2AAAAVVRERIS2bNmigoICSdLGjRsVGhqq+vXrW5zsNydOnLjoUuXzY8ePHy/vSAAqKIotAABAFdW1a1cVFBRo69atys/PV1JS0hUtQ75eCgsL5eFx4SEe5ze2Ol/IAYDjfgAAAKooHx8f3XzzzUpISFBBQYGKi4vVpUsXq2OV8PT0VFFR0QXjhYWFkn77vi0ASDyxBQAAqNK6d++u7du3Kz4+Xu3bt//D43fKU7169XTixIkLxs+PVZQl0wCsR7EFAACowjp16iQ3NzelpKT84TE95a158+bKzMxUXl5eqfHU1FRJUrNmzayIBaACotgCAABUYTVq1ND48eM1dOhQhYeHWx2nlM6dO6u4uFhr1qwpGSssLNS6devUqlUrNWzY0MJ0ACoSvmMLAABQxfXo0eOq75GXl6cVK1ZIkpKTkyVJq1atUq1atVS7dm3169evZO7u3bu1Z88eSVJOTo7Onj2rBQsWSJLatm2r0NBQSVJISIi6dOmi2bNn69SpU/L399f69et19OhRPfzww1edGUDlQbEFAADAVcvNzVVcXFypsWXLlkn67czc/yy2u3bt0vz580vNPf/aoUOHlhRbSXr00UcVFxenb7/9VqdPn1ZQUJD+/Oc/l5oDAG4ul8v1R5Oys7Pl6+urU6dOycfHpzxyAQAAoAySkpIUHh6u1157TcHBwVbHqTD279+v559/Xlu3blVYWJjVcQCUQVl6KN+xBQAAAADYGkuRAQAAcEm5ubkXPUv2PHd3d1b0AbAcxRYAAACX9Oabb2r37t2XvN6oUSPNmDGjHBMBwIUotgAAALik0aNHKzc395LXvby8yjENAFwcxRYAAACXxEZUAOyAzaMAAAAAALZGsQUAAAAA2BrFFgAAAABgaxRbAAAAAICtUWwBAAAAALZGsQUAAAAA2BrFFgAAAABgaxRbAAAAAICtUWwBAAAAALZGsQUAAAAA2BrFFgAAAABgaxRbAAAAAICtUWwBAAAAALZGsQUAAAAA2BrFFgAAAABgaxRbAAAAAICtUWwBAAAAALZGsQUAAAAA2BrFFgAAAABgaxRbAAAAAICtUWwBAAAAALZGsQUAAAAA2BrFFgAAAABgax5WBwAAAMC1c+TIEasjVCj8PoCqgWILAABQCXh7e0uSZsyYYXGSiun87wdA5USxBQAAqARCQkKUnJysnJwcq6NUON7e3goJCbE6BoDriGILAABQSVDeAFRVbB4FAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1ii0AAAAAwNYotgAAAAAAW6PYAgAAAABsjWILAAAAALA1j8uZ5HK5JEnZ2dnXNQwAAAAAANL/9c/zffT3XFaxzcnJkSQ1bdr0KmIBAAAAAFA2OTk58vX1/d05bq7LqL/FxcXKyMiQt7e33NzcrllAAAAAAAAuxuVyKScnR40bN5a7++9/i/ayii0AAAAAABUVm0cBAAAAAGyNYgsAAAAAsDWKLQAAAADA1ii2AAAAAABbo9gCAAAAAGyNYgsAAAAAsDWKLQAAAADA1v4/GJ6dbbgrgSoAAAAASUVORK5CYII=",
+      "text/plain": [
+       "<Figure size 1200x1200 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "petab_select.plot.graph_history(\n",
+    "    models=models,\n",
+    "    criterion=petab_select.Criterion.AICC,\n",
+    "    labels=labels,\n",
+    "    colors=colors,\n",
+    ");"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d87e75ab",
+   "metadata": {},
+   "source": [
+    "## Criterion values of each model\n",
+    "\n",
+    "This shows the criterion value of every calibrated model."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "bce41584",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjIAAAGwCAYAAACzXI8XAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAsCUlEQVR4nO3de3RU5b3/8c+EkOESJiFccjkkiIJAxKSKAmmVIkQSpBQ0Hi+1NlwOCg1yiQdpThHUXgLYpagrQvUo6Cox6hFo9ShUI8Qil0I0goI5kAUSCxMUSAYimUSyf3+4nJ9jEpiEJHsefL/W2msxz37mme/+Lhg+a/aePQ7LsiwBAAAYKMTuAgAAAFqKIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYKxQuwtoa/X19Tpy5Ii6desmh8NhdzkAACAAlmXp1KlTiouLU0hI05+7XPRB5siRI4qPj7e7DAAA0ALl5eXq06dPk/sv+iDTrVs3Sd80wuVy2VwNAAAIhMfjUXx8vO//8aYETZBZsmSJcnJyNGfOHC1fvlySVFNTo/vvv18FBQXyer1KS0vT008/rejo6IDX/fZ0ksvlIsgAAGCY810WEhQX++7cuVN//vOflZSU5Dc+b948vf7663r11VdVVFSkI0eO6JZbbrGpSgAAEGxsDzKnT5/WXXfdpWeffVbdu3f3jVdVVem5557TY489ptGjR2vo0KFatWqVtm7dqu3bt9tYMQAACBa2B5msrCyNHz9eqampfuPFxcWqq6vzGx80aJASEhK0bdu2Jtfzer3yeDx+GwAAuDjZeo1MQUGBPvjgA+3cubPBPrfbrbCwMEVGRvqNR0dHy+12N7lmbm6uHn744dYuFQAABCHbPpEpLy/XnDlztGbNGnXq1KnV1s3JyVFVVZVvKy8vb7W1AQBAcLEtyBQXF+vYsWO6+uqrFRoaqtDQUBUVFenJJ59UaGiooqOjVVtbq8rKSr/nVVRUKCYmpsl1nU6n7xtKfFMJAICLm22nlsaMGaM9e/b4jU2ZMkWDBg3SggULFB8fr44dO6qwsFAZGRmSpNLSUh0+fFgpKSl2lAwAAIKMbUGmW7duGjJkiN9Y165d1aNHD9/4tGnTlJ2draioKLlcLt13331KSUnRiBEj7CgZAAAEmaC5IV5jHn/8cYWEhCgjI8PvhngAAACS5LAsy7K7iLbk8XgUERGhqqoqrpcBAMAQgf7/bft9ZAAAAFqKIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLGC+uvXwe6Jk0/YXYJt5nSfY3cJAADwiQwAADAXQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGPZGmRWrFihpKQkuVwuuVwupaSk6K233vLtHzVqlBwOh982Y8YMGysGAADBJNTOF+/Tp4+WLFmiAQMGyLIsvfDCC5o4caI+/PBDXXHFFZKk6dOn65FHHvE9p0uXLnaVCwAAgoytQWbChAl+j//whz9oxYoV2r59uy/IdOnSRTExMQGv6fV65fV6fY89Hk/rFAsAAIJO0Fwjc/bsWRUUFKi6ulopKSm+8TVr1qhnz54aMmSIcnJy9NVXX51zndzcXEVERPi2+Pj4ti4dAADYxNZPZCRpz549SklJUU1NjcLDw7Vu3TolJiZKkn7xi1+ob9++iouL0+7du7VgwQKVlpZq7dq1Ta6Xk5Oj7Oxs32OPx0OYAQDgImV7kBk4cKBKSkpUVVWl//mf/1FmZqaKioqUmJioe+65xzfvyiuvVGxsrMaMGaOysjJddtllja7ndDrldDrbq3wAAGAj208thYWFqX///ho6dKhyc3OVnJysJ554otG5w4cPlyQdOHCgPUsEAABByvYg83319fV+F+t+V0lJiSQpNja2HSsCAADBytZTSzk5ORo3bpwSEhJ06tQp5efna/Pmzdq4caPKysqUn5+vm266ST169NDu3bs1b948jRw5UklJSXaWDQAAgoStQebYsWP61a9+paNHjyoiIkJJSUnauHGjbrzxRpWXl+udd97R8uXLVV1drfj4eGVkZGjhwoV2lgwAAIKIrUHmueeea3JffHy8ioqK2rEaAABgmqC7RgYAACBQBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCxbg8yKFSuUlJQkl8sll8ullJQUvfXWW779NTU1ysrKUo8ePRQeHq6MjAxVVFTYWDEAAAgmtgaZPn36aMmSJSouLtauXbs0evRoTZw4UZ988okkad68eXr99df16quvqqioSEeOHNEtt9xiZ8kAACCIOCzLsuwu4ruioqL06KOP6tZbb1WvXr2Un5+vW2+9VZL06aefavDgwdq2bZtGjBgR0Hoej0cRERGqqqqSy+Vq1VqfOPlEq65nkjnd59hdAgDgIhbo/99Bc43M2bNnVVBQoOrqaqWkpKi4uFh1dXVKTU31zRk0aJASEhK0bdu2Jtfxer3yeDx+GwAAuDjZHmT27Nmj8PBwOZ1OzZgxQ+vWrVNiYqLcbrfCwsIUGRnpNz86Olput7vJ9XJzcxUREeHb4uPj2/gIAACAXWwPMgMHDlRJSYl27NihmTNnKjMzU3v37m3xejk5OaqqqvJt5eXlrVgtAAAIJqF2FxAWFqb+/ftLkoYOHaqdO3fqiSee0O23367a2lpVVlb6fSpTUVGhmJiYJtdzOp1yOp1tXTYAAAgCtn8i83319fXyer0aOnSoOnbsqMLCQt++0tJSHT58WCkpKTZWCAAAgoWtn8jk5ORo3LhxSkhI0KlTp5Sfn6/Nmzdr48aNioiI0LRp05Sdna2oqCi5XC7dd999SklJCfgbSwAA4OJma5A5duyYfvWrX+no0aOKiIhQUlKSNm7cqBtvvFGS9PjjjyskJEQZGRnyer1KS0vT008/bWfJAAAgiATdfWRaG/eRaRvcRwYA0JaMu48MAABAcxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYtgaZ3NxcXXvtterWrZt69+6tSZMmqbS01G/OqFGj5HA4/LYZM2bYVDEAAAgmtgaZoqIiZWVlafv27Xr77bdVV1ensWPHqrq62m/e9OnTdfToUd+2bNkymyoGAADBJNTOF9+wYYPf49WrV6t3794qLi7WyJEjfeNdunRRTExMQGt6vV55vV7fY4/H0zrFAgCAoBNU18hUVVVJkqKiovzG16xZo549e2rIkCHKycnRV1991eQaubm5ioiI8G3x8fFtWjMAALCPw7Isy+4iJKm+vl4///nPVVlZqS1btvjGn3nmGfXt21dxcXHavXu3FixYoGHDhmnt2rWNrtPYJzLx8fGqqqqSy+Vq1ZqfOPlEq65nkjnd59hdAgDgIubxeBQREXHe/79tPbX0XVlZWfr444/9Qowk3XPPPb4/X3nllYqNjdWYMWNUVlamyy67rME6TqdTTqezzesFAAD2C4pTS7NmzdIbb7yhTZs2qU+fPuecO3z4cEnSgQMH2qM0AAAQxGz9RMayLN13331at26dNm/erH79+p33OSUlJZKk2NjYNq4OAAAEO1uDTFZWlvLz8/XXv/5V3bp1k9vtliRFRESoc+fOKisrU35+vm666Sb16NFDu3fv1rx58zRy5EglJSXZWToAAAgCtgaZFStWSPrmpnfftWrVKk2ePFlhYWF65513tHz5clVXVys+Pl4ZGRlauHChDdUCAIBgY/uppXOJj49XUVFRO1UDAABMExQX+wIAALQEQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYq9lBJjc3V88//3yD8eeff15Lly5tlaIAAAAC0ewg8+c//1mDBg1qMH7FFVdo5cqVrVIUAABAIJodZNxut2JjYxuM9+rVS0ePHm2VogAAAALR7CATHx+v999/v8H4+++/r7i4uFYpCgAAIBChzX3C9OnTNXfuXNXV1Wn06NGSpMLCQj3wwAO6//77W71AAACApjQ7yMyfP1/Hjx/Xr3/9a9XW1kqSOnXqpAULFug3v/lNqxcIAADQlGYHGYfDoaVLl+rBBx/Uvn371LlzZw0YMEBOp7Mt6gMAAGhSwNfIvPvuu0pMTJTH45EkhYeH69prr9WQIUNUU1OjK664Qv/4xz/arFAAAIDvCzjILF++XNOnT5fL5WqwLyIiQvfee68ee+yxVi0OAADgXAIOMh999JHS09Ob3D927FgVFxe3SlEAAACBCDjIVFRUqGPHjk3uDw0N1RdffNEqRQEAAAQi4CDzb//2b/r444+b3L979+5Gb5QHAADQVgIOMjfddJMefPBB1dTUNNh35swZLV68WD/72c9atTgAAIBzCfjr1wsXLtTatWt1+eWXa9asWRo4cKAk6dNPP1VeXp7Onj2r3/72t21WKAAAwPcFHGSio6O1detWzZw5Uzk5ObIsS9I395VJS0tTXl6eoqOj26xQAACA72vWDfH69u2rN998UydPntSBAwdkWZYGDBig7t27t1V9AAAATWr2j0ZKUvfu3XXttddq2LBhFxRicnNzde2116pbt27q3bu3Jk2apNLSUr85NTU1ysrKUo8ePRQeHq6MjAxVVFS0+DUBAMDFo0VBprUUFRUpKytL27dv19tvv626ujqNHTtW1dXVvjnz5s3T66+/rldffVVFRUU6cuSIbrnlFhurBgAAwSLgU0uBhoe1a9cG/OIbNmzwe7x69Wr17t1bxcXFGjlypKqqqvTcc88pPz/f90vbq1at0uDBg7V9+3aNGDEi4NcCAAAXn4CDTERERFvWIUmqqqqSJEVFRUmSiouLVVdXp9TUVN+cQYMGKSEhQdu2bWs0yHi9Xnm9Xt/jb38bCgAAXHwCDjKrVq0675xz3TDvfOrr6zV37lz95Cc/0ZAhQyRJbrdbYWFhioyM9JsbHR0tt9vd6Dq5ubl6+OGHW1wHAAAwxwVfI3Pq1Ck988wzGj58uJKTk1u8TlZWlj7++GMVFBRcUD05OTmqqqrybeXl5Re0HgAACF4tDjLvvfeeMjMzFRsbqz/96U+64YYbtH379hatNWvWLL3xxhvatGmT+vTp4xuPiYlRbW2tKisr/eZXVFQoJiam0bWcTqdcLpffBgAALk7Nuo+M2+3W6tWr9dxzz8nj8ei2226T1+vV+vXrlZiY2OwXtyxL9913n9atW6fNmzerX79+fvuHDh2qjh07qrCwUBkZGZKk0tJSHT58WCkpKc1+PQAAcHEJ+BOZCRMmaODAgdq9e7eWL1+uI0eO6KmnnrqgF8/KytJf/vIX5efnq1u3bnK73XK73Tpz5oykby4wnjZtmrKzs7Vp0yYVFxdrypQpSklJ4RtLAAAg8E9k3nrrLc2ePVszZ87UgAEDWuXFV6xYIUkaNWqU3/iqVas0efJkSdLjjz+ukJAQZWRkyOv1Ki0tTU8//XSrvD4AADBbwEFmy5Yteu655zR06FANHjxYd999t+64444LevFvf6/pXDp16qS8vDzl5eVd0GsBAICLT8CnlkaMGKFnn31WR48e1b333quCggLFxcWpvr5eb7/9tk6dOtWWdQIAADTQ7G8tde3aVVOnTtWWLVu0Z88e3X///VqyZIl69+6tn//8521RIwAAQKMu6D4yAwcO1LJly/T555+roKDA7zeSAAAA2lqzvn7dmFOnTumll17Sf//3f6u4uLg1agIAAAhIq9wQ77e//a3i4+Nbsy4AAIDzalaQcbvdWrJkiQYMGKCbbrpJX3/9tV555RUdPXqU3zcCAADtLuBTSxMmTFBhYaFuuOEGPfTQQ5o0aZK6du3q2+9wONqkQAAAgKYEHGT+93//V7/4xS80d+5cXXPNNW1ZEwAAQEACPrW0detWde7cWaNHj9bAgQP1yCOPqKysrC1rAwAAOKcW3RBvwYIF+vvf/67LL79cI0aM0FNPPaWKioq2rBMAAKCBC7oh3t69ezVy5Ej98Y9/VGpqalvUBwAA0KRWuyHe2rVrNX78+NaqCwAA4LwuKMh8q0OHDpo0aZL+9re/tcZyAAAAAWmVIAMAAGAHggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABj2Rpk3nvvPU2YMEFxcXFyOBxav3693/7JkyfL4XD4benp6fYUCwAAgo6tQaa6ulrJycnKy8trck56erqOHj3q21566aV2rBAAAASzUDtffNy4cRo3btw55zidTsXExAS8ptfrldfr9T32eDwtrg8AAAS3oL9GZvPmzerdu7cGDhyomTNn6vjx4+ecn5ubq4iICN8WHx/fTpUCAID2FtRBJj09XS+++KIKCwu1dOlSFRUVady4cTp79myTz8nJyVFVVZVvKy8vb8eKAQBAe7L11NL53HHHHb4/X3nllUpKStJll12mzZs3a8yYMY0+x+l0yul0tleJAADARkH9icz3XXrpperZs6cOHDhgdykAACAIGBVkPv/8cx0/flyxsbF2lwIAAIKAraeWTp8+7ffpysGDB1VSUqKoqChFRUXp4YcfVkZGhmJiYlRWVqYHHnhA/fv3V1pamo1VAwCAYGFrkNm1a5duuOEG3+Ps7GxJUmZmplasWKHdu3frhRdeUGVlpeLi4jR27Fj97ne/4xoYAAAgyeYgM2rUKFmW1eT+jRs3tmM1AADANEZdIwMAAPBdBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCxbg8x7772nCRMmKC4uTg6HQ+vXr/fbb1mWFi1apNjYWHXu3Fmpqanav3+/PcUCAICgY2uQqa6uVnJysvLy8hrdv2zZMj355JNauXKlduzYoa5duyotLU01NTXtXCkAAAhGoXa++Lhx4zRu3LhG91mWpeXLl2vhwoWaOHGiJOnFF19UdHS01q9frzvuuKM9SwUAAEEoaK+ROXjwoNxut1JTU31jERERGj58uLZt29bk87xerzwej98GAAAuTrZ+InMubrdbkhQdHe03Hh0d7dvXmNzcXD388MNtWhsAXMxeeeUVu0uwzW233WZ3CWimoP1EpqVycnJUVVXl28rLy+0uCQAAtJGgDTIxMTGSpIqKCr/xiooK377GOJ1OuVwuvw0AAFycgjbI9OvXTzExMSosLPSNeTwe7dixQykpKTZWBgAAgoWt18icPn1aBw4c8D0+ePCgSkpKFBUVpYSEBM2dO1e///3vNWDAAPXr108PPvig4uLiNGnSJPuKBgAAQcPWILNr1y7dcMMNvsfZ2dmSpMzMTK1evVoPPPCAqqurdc8996iyslLXXXedNmzYoE6dOtlVMgAACCK2BplRo0bJsqwm9zscDj3yyCN65JFH2rEqAABgiqC9RgYAAOB8CDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIwVtD8aCQCASX6oP7Zp9w9t8okMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGCuog89BDD8nhcPhtgwYNsrssAAAQJELtLuB8rrjiCr3zzju+x6GhQV8yAABoJ0GfCkJDQxUTExPwfK/XK6/X63vs8XjaoiwAABAEgvrUkiTt379fcXFxuvTSS3XXXXfp8OHD55yfm5uriIgI3xYfH99OlQIAgPYW1EFm+PDhWr16tTZs2KAVK1bo4MGDuv7663Xq1Kkmn5OTk6OqqirfVl5e3o4VAwCA9hTUp5bGjRvn+3NSUpKGDx+uvn376pVXXtG0adMafY7T6ZTT6WyvEgEAgI2C+hOZ74uMjNTll1+uAwcO2F0KAAAIAkYFmdOnT6usrEyxsbF2lwIAAIJAUAeZ//zP/1RRUZEOHTqkrVu36uabb1aHDh1055132l0aAAAIAkF9jcznn3+uO++8U8ePH1evXr103XXXafv27erVq5fdpQEAgCAQ1EGmoKDA7hIAAEAQC+pTSwAAAOdCkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgLIIMAAAwFkEGAAAYiyADAACMRZABAADGIsgAAABjEWQAAICxCDIAAMBYBBkAAGAsggwAADAWQQYAABiLIAMAAIxFkAEAAMYiyAAAAGMRZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjBVqdwH44Xni5BN2l2CLOd3n2F0CAFx0jAgyeXl5evTRR+V2u5WcnKynnnpKw4YNs7ssoF298sordpdgi9tuu83uEgAEsaA/tfTyyy8rOztbixcv1gcffKDk5GSlpaXp2LFjdpcGAABsFvRB5rHHHtP06dM1ZcoUJSYmauXKlerSpYuef/55u0sDAAA2C+pTS7W1tSouLlZOTo5vLCQkRKmpqdq2bVujz/F6vfJ6vb7HVVVVkiSPx9Pq9dV4alp9TVN4OrS8nz/Uvl1IzyTpq6++aqVKzNIW/3Zxbj/Uv2vShf19+6H2ra3+jX67rmVZ555oBbF//etfliRr69atfuPz58+3hg0b1uhzFi9ebEliY2NjY2Njuwi28vLyc2aFoP5EpiVycnKUnZ3te1xfX68TJ06oR48ecjgcNlbWujwej+Lj41VeXi6Xy2V3OUagZy1D31qGvrUMfWu+i7VnlmXp1KlTiouLO+e8oA4yPXv2VIcOHVRRUeE3XlFRoZiYmEaf43Q65XQ6/cYiIyPbqkTbuVyui+ovbnugZy1D31qGvrUMfWu+i7FnERER550T1Bf7hoWFaejQoSosLPSN1dfXq7CwUCkpKTZWBgAAgkFQfyIjSdnZ2crMzNQ111yjYcOGafny5aqurtaUKVPsLg0AANgs6IPM7bffri+++EKLFi2S2+3Wj370I23YsEHR0dF2l2Yrp9OpxYsXNziNhqbRs5ahby1D31qGvjXfD71nDss63/eaAAAAglNQXyMDAABwLgQZAABgLIIMAAAwFkEGAAAYiyDTDiZPniyHw6EZM2Y02JeVlSWHw6HJkycHtFZeXp4uueQSderUScOHD9c///lPv/01NTXKyspSjx49FB4eroyMjAY3FDx8+LDGjx+vLl26qHfv3po/f76+/vrrFh9fWwm2vs2ePVtDhw6V0+nUj370o5YeVptrz74988wzGjVqlFwulxwOhyorKxusceLECd11111yuVyKjIzUtGnTdPr06ZYcWptprZ699957mjBhguLi4uRwOLR+/foGcyzL0qJFixQbG6vOnTsrNTVV+/fv95tjQs+k4OvbH/7wB/34xz9Wly5dgvpGqO3Zt7Vr12rs2LG+u9uXlJQ0mBPI+18wI8i0k/j4eBUUFOjMmTO+sZqaGuXn5yshISGgNV5++WVlZ2dr8eLF+uCDD5ScnKy0tDQdO3bMN2fevHl6/fXX9eqrr6qoqEhHjhzRLbfc4tt/9uxZjR8/XrW1tdq6dateeOEFrV69WosWLWq9g21FwdK3b02dOlW33377hR9YG2uvvn311VdKT0/Xf/3XfzW5zl133aVPPvlEb7/9tt544w299957uueee1p+cG2kNXpWXV2t5ORk5eXlNTln2bJlevLJJ7Vy5Urt2LFDXbt2VVpammpq/v+PqZrSMym4+lZbW6t///d/18yZM1t+QO2kvfpWXV2t6667TkuXLm1yTqDvf0GrNX7cEeeWmZlpTZw40RoyZIj1l7/8xTe+Zs0aKykpyZo4caKVmZl53nWGDRtmZWVl+R6fPXvWiouLs3Jzcy3LsqzKykqrY8eO1quvvuqbs2/fPkuStW3bNsuyLOvNN9+0QkJCLLfb7ZuzYsUKy+VyWV6v90IPtVUFU9++a/HixVZycnLLD6yNtVffvmvTpk2WJOvkyZN+43v37rUkWTt37vSNvfXWW5bD4bD+9a9/Nf/g2khr9ey7JFnr1q3zG6uvr7diYmKsRx991DdWWVlpOZ1O66WXXrIsy5yeWVZw9e27Vq1aZUVERDTrddtTe/Xtuw4ePGhJsj788EO/8ea+/wUjPpFpR1OnTtWqVat8j59//vmA71BcW1ur4uJipaam+sZCQkKUmpqqbdu2SZKKi4tVV1fnN2fQoEFKSEjwzdm2bZuuvPJKvxsKpqWlyePx6JNPPrmg42srwdA3E7V13wKxbds2RUZG6pprrvGNpaamKiQkRDt27Ah4nfZyIT0LxMGDB+V2u/36GhERoeHDh/v9GzWpZ1Jw9M1Ebd23QFwM738EmXb0y1/+Ulu2bNFnn32mzz77TO+//75++ctfBvTcL7/8UmfPnm1wR+Po6Gi53W5JktvtVlhYWINzw9+f09ga3+4LRsHQNxO1dd8C4Xa71bt3b7+x0NBQRUVFBWVvL6Rngfj2mM/399GknknB0TcTtXXfAnExvP8F/U8UXEx69eql8ePHa/Xq1bIsS+PHj1fPnj3tLivo0beWoW/NR89ahr61DH1rHQSZdjZ16lTNmjVLks55gdb39ezZUx06dGhwJXlFRYViYmIkSTExMaqtrVVlZaVfuv7+nO9/8+TbNb+dE4zs7pup2rJvgYiJifG7OFiSvv76a504cSJoe9vSngXi22OuqKhQbGysb7yiosL3TTgTeybZ3zdTtWXfAnExvP9xaqmdpaenq7a2VnV1dUpLSwv4eWFhYRo6dKgKCwt9Y/X19SosLFRKSookaejQoerYsaPfnNLSUh0+fNg3JyUlRXv27PF7o3z77bflcrmUmJh4oYfXZuzum6nasm+BSElJUWVlpYqLi31j7777rurr6zV8+PCA12lPLe1ZIPr166eYmBi/vno8Hu3YscPv36hpPZPs75up2rJvgbgY3v/4RKaddejQQfv27fP9uTmys7OVmZmpa665RsOGDdPy5ctVXV3tuzgsIiJC06ZNU3Z2tqKiouRyuXTfffcpJSVFI0aMkCSNHTtWiYmJuvvuu7Vs2TK53W4tXLhQWVlZQf3LqXb3TZIOHDig06dPy+1268yZM777MSQmJiosLKx1DrSVtWXfpG/Or7vdbh04cECStGfPHnXr1k0JCQmKiorS4MGDlZ6erunTp2vlypWqq6vTrFmzdMcddyguLq71DrQVXUjPTp8+7euF9M1FqiUlJYqKilJCQoIcDofmzp2r3//+9xowYID69eunBx98UHFxcZo0aZIkGdkzyf6+Sd/cI+vEiRM6fPiwzp496/s32r9/f4WHh1/wMbaFtuybJF8/jhw5IumbkCJ980lMTExMwO9/Qc3Or0z9UHz7VbumNOerdk899ZSVkJBghYWFWcOGDbO2b9/ut//MmTPWr3/9a6t79+5Wly5drJtvvtk6evSo35xDhw5Z48aNszp37mz17NnTuv/++626urrmHlabC7a+/fSnP7UkNdgOHjzYzCNrW+3Zt8WLFzfak1WrVvnmHD9+3Lrzzjut8PBwy+VyWVOmTLFOnTrVgiNrO63Vs2+/hv797bvPra+vtx588EErOjracjqd1pgxY6zS0lK/dUzomWUFX98yMzMbXWfTpk0tO8A20p59W7VqVaNzFi9e7JsTyPtfMHNYlmW1cVYCAABoE1wjAwAAjEWQCRKHDx9WeHh4k9vhw4ftLjEo0beWoW/NR89ahr61DH0LHKeWgsTXX3+tQ4cONbn/kksuUWgo12Z/H31rGfrWfPSsZehby9C3wBFkAACAsTi1BAAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAGyxefNmORwOVVZWBvycSy65RMuXL29y/+TJk/1uWX8+hw4dksPh8N3KvqVGjRqluXPnXtAaAFqGIAOggcmTJ8vhcGjGjBkN9mVlZcnhcGjy5MntXxgAfA9BBkCj4uPjVVBQoDNnzvjGampqlJ+f7/tBOgCwG0EGQKOuvvpqxcfHa+3atb6xtWvXKiEhQVdddZXfXK/Xq9mzZ6t3797q1KmTrrvuOu3cudNvzptvvqnLL79cnTt31g033NDozb62bNmi66+/Xp07d1Z8fLxmz56t6urqFh/Dhg0bdN111ykyMlI9evTQz372M5WVlTWY9+mnn+rHP/6xOnXqpCFDhqioqMhv/8cff6xx48YpPDxc0dHRuvvuu/Xll182+bpPP/20BgwYoE6dOik6Olq33npri48BwLkRZAA0aerUqVq1apXv8fPPP68pU6Y0mPfAAw/otdde0wsvvKAPPvhA/fv3V1pamk6cOCFJKi8v1y233KIJEyaopKRE//Ef/6Hf/OY3fmuUlZUpPT1dGRkZ2r17t15++WVt2bJFs2bNanH91dXVys7O1q5du1RYWKiQkBDdfPPNqq+v95s3f/583X///frwww+VkpKiCRMm6Pjx45KkyspKjR49WldddZV27dqlDRs2qKKiQrfddlujr7lr1y7Nnj1bjzzyiEpLS7VhwwaNHDmyxccA4Dzs/OltAMEpMzPTmjhxonXs2DHL6XRahw4dsg4dOmR16tTJ+uKLL6yJEydamZmZlmVZ1unTp62OHTtaa9as8T2/trbWiouLs5YtW2ZZlmXl5ORYiYmJfq+xYMECS5J18uRJy7Isa9q0adY999zjN+cf//iHFRISYp05c8ayLMvq27ev9fjjj5+37qZ88cUXliRrz549lmVZ1sGDBy1J1pIlS3xz6urqrD59+lhLly61LMuyfve731ljx471W6e8vNySZJWWllqWZVk//elPrTlz5liWZVmvvfaa5XK5LI/H02QdAFoPP9QAoEm9evXS+PHjtXr1almWpfHjx6tnz55+c8rKylRXV6ef/OQnvrGOHTtq2LBh2rdvnyRp3759Gj58uN/zUlJS/B5/9NFH2r17t9asWeMbsyxL9fX1OnjwoAYPHtzs+vfv369FixZpx44d+vLLL32fxBw+fFhDhgxptJbQ0FBdc801vto/+ugjbdq0SeHh4Q3WLysr0+WXX+43duONN6pv37669NJLlZ6ervT0dN18883q0qVLs+sHcH4EGQDnNHXqVN/pnby8vDZ7ndOnT+vee+/V7NmzG+xr6cXFEyZMUN++ffXss88qLi5O9fX1GjJkiGpra5tV14QJE7R06dIG+2JjYxuMdevWTR988IE2b96sv//971q0aJEeeugh7dy5U5GRkS06DgBN4xoZAOeUnp6u2tpa1dXVKS0trcH+yy67TGFhYXr//fd9Y3V1ddq5c6cSExMlSYMHD9Y///lPv+dt377d7/HVV1+tvXv3qn///g22sLCwZtd9/PhxlZaWauHChRozZowGDx6skydPNjr3u7V8/fXXKi4u9n0CdPXVV+uTTz7RJZdc0qCurl27NrpeaGioUlNTtWzZMu3evVuHDh3Su+++2+xjAHB+BBkA59ShQwft27dPe/fuVYcOHRrs79q1q2bOnKn58+drw4YN2rt3r6ZPn66vvvpK06ZNkyTNmDFD+/fv1/z581VaWqr8/HytXr3ab50FCxZo69atmjVrlkpKSrR//3799a9/bfHFvt27d1ePHj30zDPP6MCBA3r33XeVnZ3d6Ny8vDytW7dOn376qbKysnTy5ElNnTpV0jf3zTlx4oTuvPNO7dy5U2VlZdq4caOmTJmis2fPNljrjTfe0JNPPqmSkhJ99tlnevHFF1VfX6+BAwe26DgAnBtBBsB5uVwuuVyuJvcvWbJEGRkZuvvuu3X11VfrwIED2rhxo7p37y7pm1NDr732mtavX6/k5GStXLlSf/zjH/3WSEpKUlFRkf7v//5P119/va666iotWrRIcXFxLao5JCREBQUFKi4u1pAhQzRv3jw9+uijTda/ZMkSJScna8uWLfrb3/7muxYoLi5O77//vs6ePauxY8fqyiuv1Ny5cxUZGamQkIZvoZGRkVq7dq1Gjx6twYMHa+XKlXrppZd0xRVXtOg4AJybw7Isy+4iAAAAWoJPZAAAgLEIMgAAwFgEGQAAYCyCDAAAMBZBBgAAGIsgAwAAjEWQAQAAxiLIAAAAYxFkAACAsQgyAADAWAQZAABgrP8HjWjJuiKnxqYAAAAASUVORK5CYII=",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "petab_select.plot.bar_criterion_vs_models(\n",
+    "    models=models,\n",
+    "    criterion=petab_select.Criterion.AICC,\n",
+    "    labels=labels,\n",
+    "    colors=colors,\n",
+    ");"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d9c2d487",
+   "metadata": {},
+   "source": [
+    "## Criterion values vs. number of estimated parameters\n",
+    "\n",
+    "This shows all calibrated models.\n",
+    "\n",
+    "In this example, models with 2 estimated parameters tend to perform best. This is also seen in the UpSet plot above.\n",
+    "\n",
+    "Jitter is added to distinguish models with the same number of parameters and similar criterion values, according to the optional `max_jitter` argument."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "824e2e6a",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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Tp1UWlqqVatWKSsrSxs2bFB+fr5WrVqlW2+9VR06dND+/fs1e/ZsDRs2TP369fNn2QAAIAD5NdQcP35cP/zhD1VYWCin06l+/fppw4YNGj16tAoKCvTBBx9o2bJlKi8vV2JioiZNmqSHH37YnyUDAIAAFXBzahpbQ+7JAQAA/2rI9TtgnlMDAABwJQg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEgg1AADAEvwaap5//nn169dPDodDDodDqampev/99z3bKyoqlJ6erg4dOigsLEyTJk1ScXGxHysGAACByq+hJiEhQYsXL1ZOTo52796tESNGaMKECfr4448lSbNnz9af/vQnvfHGG8rOztaxY8d02223+bNkAAAQoGzGGOPvIi4UGRmpJ598UpMnT1ZUVJRWrVqlyZMnS5IOHTqknj17avv27RoyZIhP/blcLjmdTpWUlMjhcDRl6QAAoJE05PodMHNqqqurtXr1apWXlys1NVU5OTmqqqrSqFGjPPv06NFDnTp10vbt2+vsx+12y+VyeS0AAMD6/B5qPvroI4WFhclut+tnP/uZ1q5dq169eqmoqEht2rRRRESE1/4xMTEqKiqqs7/MzEw5nU7PkpiY2MSvAAAABAK/h5qUlBTt3btXO3fu1D333KMpU6bok08+aXB/GRkZKikp8SwFBQWNWC0AAAhUwf4uoE2bNurWrZskaeDAgdq1a5eefvppff/731dlZaVOnz7tNVpTXFys2NjYOvuz2+2y2+1NXTYAAAgwfh+p+aaamhq53W4NHDhQrVu31qZNmzzbcnNzdfToUaWmpvqxQgAAEIj8OlKTkZGhcePGqVOnTiotLdWqVauUlZWlDRs2yOl0avr06ZozZ44iIyPlcDg0c+ZMpaam+vzJJwAAcPXwa6g5fvy4fvjDH6qwsFBOp1P9+vXThg0bNHr0aEnSb37zGwUFBWnSpElyu91KS0vTc88958+SAQBA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Dg7WoEGDPOs9evRQRESEDh48qBtuuEFlZWVasGCB3n33XRUWFurcuXM6e/asjh496tXP9ddff1Hfr7/+up555hnl5+errKxM586dk8Ph8Nqnc+fOXq8rJiZGrVq1uujP6fn3cN++fSorK7toPtXZs2eVn59f5+v09bikpCRFRUV51vv376+RI0eqb9++SktL05gxYzR58mS1b9++znMBviDU4Ko2bNgwpaWlKSMjQ1OnTvXaFhQUdNHF68K5C+e1bt3aa91ms9XaVlNT43NdZWVlGj9+vJYsWXLRtri4OM+v27Vr53OfV6KsrEyS9O677+pb3/qW1za73S5JGjdunD7//HO999572rhxo0aOHKn09HQ99dRT9TrX5d7P859QO/9+rl69Wvfff79+/etfKzU1VeHh4XryySe1c+fOy76mgQMH6tVXX71o24UX4KZw//33a+PGjXrqqafUrVs3hYaGavLkyRdNov3m7+/27dt11113aeHChUpLS5PT6dTq1av161//2mu/+v6ZLCsrU1xcnGeu0YUuNZHb1+O++TpatWqljRs3atu2bfrzn/+sZ599Vg899JB27tzpmX8FNAShBle9xYsXa8CAAUpJSfFqj4qKUlFRkYwxngtpYz5bZseOHRo2bJgk6dy5c8rJydGMGTMkSdddd53++Mc/qnPnzgoObviPqcPhUHx8vLZu3apvf/vbnvatW7fqhhtu8LmfXr16yW636+jRo179fFNUVJSmTJmiKVOm6Oabb9YDDzygp556Sm3atJH09cTbxrZ161bdeOONuvfeez1t3xxdaNOmzUXnvu666/T6668rOjr6opGO8+Li4rRz586Lfp+uu+66S9Z07tw57d692/Me5+bm6vTp0+rZs6en5qlTp+q73/2upK/DwYUTlOuybds2JSUl6aGHHvK0ff7555c97nKuu+46FRUVKTg42Gsi/IXqeg8vd1xdbDabhg4dqqFDh+rRRx9VUlKS1q5dqzlz5jTwVQB8+glQ3759ddddd+mZZ57xar/lllv05Zdf6oknnlB+fr6WL1/u9ambK7V8+XKtXbtWhw4dUnp6uk6dOqUf/ehHkqT09HR99dVXuuOOO7Rr1y7l5+drw4YNmjZtWr2DwQMPPKAlS5bo9ddfV25urn7xi19o7969uu+++3zuIzw8XPfff79mz56tV155Rfn5+dqzZ4+effZZvfLKK5KkRx99VG+99Zby8vL08ccf65133vFcxKOjoxUaGqr169eruLhYJSUl9XoNl5KcnKzdu3drw4YNOnz4sB555BHt2rXLa5/OnTtr//79ys3N1YkTJ1RVVaW77rpLHTt21IQJE/S3v/1NR44cUVZWln7+85/riy++kCTdd999Wrx4sdatW6dDhw7p3nvv1enTpy9bU+vWrTVz5kzt3LlTOTk5mjp1qoYMGeIJOcnJyVqzZo327t2rffv26c477/RpJC85OVlHjx7V6tWrlZ+fr2eeeUZr166t/5v2DaNGjVJqaqomTpyoP//5z/rss8+0bds2PfTQQ9q9e7ekr9/DI0eOaO/evTpx4oTcbrdPx9Vm586d+tWvfqXdu3fr6NGjWrNmjb788kvPnxegoQg1gKTHHnvsootKz5499dxzz2n58uXq37+//v73v1/R3JtvWrx4sRYvXqz+/ftry5Ytevvtt9WxY0dJ8oyuVFdXa8yYMerbt69mzZqliIgIr3kRvvj5z3+uOXPmaO7cuerbt6/Wr1+vt99+W8nJyfXqZ9GiRXrkkUeUmZmpnj17auzYsXr33Xc9twvatGmjjIwM9evXT8OGDVOrVq20evVqSV/PMXnmmWf04osvKj4+XhMmTKjXuS/lpz/9qW677TZ9//vf1+DBg3Xy5EmvURtJuvvuu5WSkqLrr79eUVFR2rp1q9q2bavNmzerU6dOuu2229SzZ09Nnz5dFRUVnpGbuXPn6gc/+IGmTJniubV1fnTlUtq2bat58+bpzjvv1NChQxUWFqbXX3/ds33p0qVq3769brzxRo0fP15paWmXHf2RpH//93/X7NmzNWPGDA0YMEDbtm3TI488Us937GI2m03vvfeehg0bpmnTpql79+66/fbb9fnnn3vmME2aNEljx47V8OHDFRUVpddee82n42rjcDi0efNm3Xrrrerevbsefvhh/frXv9a4ceOu+LXg6mYzl5vxBgDw2cqVKzVr1iyfRnQANC5GagAAgCUQagAAgCVw+wkAAFgCIzUAAMASCDUAAMASCDUAAMASCDUAAMASCDUAAMASCDUAAMASCDUAAMASCDUAAMASCDUAAMAS/h+a8TAoH6v8pwAAAABJRU5ErkJggg==",
+      "text/plain": [
+       "<Figure size 640x480 with 1 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "petab_select.plot.scatter_criterion_vs_n_estimated(\n",
+    "    models=models,\n",
+    "    criterion=petab_select.Criterion.AICC,\n",
+    "    labels=labels,\n",
+    "    colors=colors,\n",
+    "    # Uncomment to turn off jitter.\n",
+    "    # max_jitter=0,\n",
+    ");"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8dc7e142",
+   "metadata": {},
+   "source": [
+    "## History as layers in a hierarchical graph\n",
+    "\n",
+    "This shows the relative change in parameters of each model, compared to its predecessor model.\n",
+    "\n",
+    "N.B.: this may give a misleading impression of the models calibrated in each iteration, since it's only based on \"predecessor model\" relationships. In this example, each layer is indeed an iteration."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "id": "5ce191fc",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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44036oMPPpAkVapUKXc9hRYAJeHqq6/Whx9+KCn7A6ijOAcBsM24aHErxjgAAAAALmOMUUxMjC688EL99NNPGjZsmObPn5+v6AIAxSk2NlaNGzfWL7/8os8++0zTp09XjRo1bMcCAJyCY9w6TRgAAADwU5MnT1abNm00Y8YMtW7d2nYcAH5m1qxZat26tSZOnKg2bdrYjgPAzyUmJioyMlI//PGDQsNDbcfJdTjpsK5vcr0SEhIUERFhO04uir0AAACAi2RlZalZs2aqVq2aZs2aZTsOAD/j9Xp12WWXKSgoSAsWLJDjOLYjAfBzR4u9s1bPcl2x94Zzb3BdsZeZvQAAAICLfPbZZ1q9erUGDBhgOwoAPzR+/HgtX75cAwYMoNALAEVo6NChatasmSIiIhQREaGWLVvq+++/z33+yJEj6ty5sypVqqSwsDC1bdtWu3fvLvRxKPYCAAAALpGamqru3bvr/vvv1yWXXGI7DgA/k5GRoa5du+q2227TVVddZTsOAPiUmjVrasCAAVq+fLmWLVum6667TnfddZdWrVolSXrppZf07bffaty4cZo7d6527Nihe+65p9DH4QZtAAAAgEt88MEH2rVrl/r06WM7CgA/NGzYMG3YsEETJkywHQUAjmFyFrc4miUxMTHf+uDgYAUHBx+z/R133JHv5759+2ro0KFatGiRatasqU8++USjR4/WddddJ0kaMWKEmjRpokWLFqlFixYFzkVnLwAAAOAChw4dUr9+/dSpUyc1aNDAdhwAfiY5OVk9e/bUI488ovPPP992HAAoNWrVqqXIyMjcr/79+5/yNVlZWRo7dqwOHz6sli1bavny5crIyNANN9yQu03jxo1Vu3ZtLVy4sFB56OwFAAAAXOD1119XWlqaunfvbjsKAD/0zjvv6ODBg+rVq5ftKABQqmzdujXfDdqO19V71O+//66WLVvqyJEjCgsL08SJE3Xuuefq119/VVBQkMqXL59v+ypVqmjXrl2FykOxFwAAALBs+/btGjx4sF555RVVrVrVdhwAfmbfvn0aOHCgOnfurDp16tiOAwDHZYyRMS4a45CT5egN1wqiUaNG+vXXX5WQkKDx48fr0Ucf1dy5c4s0F8VeAAAAwLKePXuqXLlyevXVV21HAeCH+vbtK8dxFBsbazsKAPi0oKAg1a9fX5J08cUXa+nSpRo8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srJ0wQUXqEqVKpo1a5Ycx7EdCYAfMcbosssuU0BAgBYuXMg5CACKUGJioiIjIzV80XCVC3PPiJyU5BQ93uJxJSQkKCIiwnacXHzcCAAAgFLv888/16pVqzRixAiKLABK3Pjx47Vs2TLNmTOHcxAAFBNjjNzUs+qmLHlxfRsAAABKtSNHjqh79+669957demll9qOA8DPZGRkqGvXrrrlllt09dVX244DAPBzdPYCAACgVPvggw+0Y8cO9e3b13YUAH7ok08+0fr16zVu3DjbUQAAoNgLAACA0uvQoUPq27ev/vOf/6hhw4a24wDwM4cPH1bPnj318MMP64ILLrAdBwB8mslZ3MJNWfJijAMAAABKrYEDByotLU09evSwHQWAH3rnnXd04MAB9e7d23YUAIDL9e/fX5deeqnCw8NVuXJltWnTRmvWrMm3zTXXXCPHcfJ9PfXUU4U6DsVeAAAAlEo7duzQO++8oxdffFHVqlWzHQeAn9m3b58GDhyop59+WmeffbbtOAAAl5s7d646d+6sRYsWaebMmcrIyNCNN96ow4cP59uuU6dO2rlzZ+7XwIEDC3UcxjgAAACgVOrZs6fKli2r1157zXYUAH6oX79+Msaoa9eutqMAgF8wxsgY94xOKGyWadOm5ft55MiRqly5spYvX66rrroqd325cuVUtWrV085FZy8AAABKnTVr1uiTTz5R165dFRkZaTsOAD/z119/6YMPPtCrr76qs846y3YcAIBFiYmJ+b7S0tIK9LqEhARJUsWKFfOtHzVqlKKiotS0aVPFxMQoJSWlUHno7AUAAECp07VrV1WvXl3PPPOM7SgA/FCPHj1UoUIFvfTSS7ajAAAsq1WrVr6fe/Toofj4+JO+xuv16sUXX1SrVq3UtGnT3PUPPfSQ6tSpo+rVq2vFihXq0qWL1qxZowkTJhQ4D8VeAAAAlCqLFy/W119/rZEjRyokJMR2HAB+5vfff9dnn32m999/X2FhYbbjAIDfMDmLWxzNsnXrVkVEROSuDw4OPuVrO3furJUrV2r+/Pn51j/xxBO5359//vmqVq2arr/+em3YsEH16tUrUC6KvQAAACg1jDGKjo5W06ZN9e9//9t2HAB+KDY2Vuecc446depkOwoAwAUiIiLyFXtP5dlnn9WUKVM0b9481axZ86TbXn755ZKk9evXU+wFAACA75k+fbrmzJmjb7/9VgEBAbbjAPAzP/74o6ZMmaKxY8cqMDDQdhwAQClijNFzzz2niRMnas6cOapbt+4pX/Prr79KkqpVq1bg4zjGTbexAwAAAE7A6/XqoosuUnh4uObNmyfHcWxHAuBHjDFq1aqV0tLStHTpUnk83O8cAEpCYmKiIiMj9dGCj1Q2rKztOLlSk1P1RKsnlJCQUKDO3meeeUajR4/W5MmT1ahRo9z1kZGRKlu2rDZs2KDRo0fr1ltvVaVKlbRixQq99NJLqlmzpubOnVvgXHT2AgAAoFQYM2aMfvvtNy1YsIBCL4AS980332jhwoWaMWMGhV4AQKENHTpUknTNNdfkWz9ixAh16NBBQUFBmjVrlt555x0dPnxYtWrVUtu2bRUXF1eo49DZCwAAANdLS0tT48aNdcEFF2jSpEm24wDwM5mZmWrWrJlq1KihmTNn2o4DAH7FVzp7SwqdvQAAAHC9Dz/8UFu2bNF3331nOwoAP/TZZ5/pjz/+0Oeff247CgD4LZOzuIWbsuTFtScAAABwtcTERPXu3VsdOnTQueeeazsOAD+Tmpqq7t27q127drr44ottxwEA4KQo9gIAAMDVBg0apKSkJMXHx9uOAsAPvf/++9q9e7f69OljOwoAAKfEGAcAAAC41u7duzVo0CA999xzqlWrlu04APzMwYMH1a9fP3Xq1En169e3HQcA/JoxRm669ZibsuRFZy8AAABcq3fv3ipTpoxiYmJsRwHgh15//XWlp6ere/futqMAAFAgFHsBAADgShs2bNCHH36o6OhoVaxY0XYcAH5m+/btGjx4sF5++WVVrVrVdhwAAAqEMQ4AAABwpW7duqly5cp6/vnnbUcB4Ifi4+MVGhqqV1991XYUAIAkk7O4hZuy5EWxFwAAAK7z888/a8yYMfroo49Urlw523EA+Jk///xTw4cP16BBgxQREWE7DgAABcYYBwAAALhOTEyMGjVqpMcee8x2FAB+KDY2VrVq1dLTTz9tOwoAAIVCZy8AAABc5YcfftCMGTP09ddfq0wZ/roKoGQtWrRIEydO1Geffabg4GDbcQAAOYwxMsY9oxPclCUvx7g1GQAAAPyOMUaXXXaZAgICtHDhQjmOYzsSAD9ijNE111yjgwcP6pdfflFAQIDtSADg9xITExUZGakhPw5R2bCytuPkSk1O1TNXPqOEhARXjfyhVQIAAACuMX78eC1btkxz5syh0AugxH3//feaN2+evvvuOwq9AIBSiWIvAAAAXCEjI0OxsbG65ZZbdPXVV9uOA8DPZGVlKTo6WldddZVuueUW23EAAP9gcha3cFOWvCj2AgAAwBU++eQTbdiwQePHj7cdBYAfGj16tH7//XdGyAAASjWP7QAAAADA4cOH1bNnTz388MO64IILbMcB4GfS0tLUrVs33X333WrRooXtOAAAnDY6ewEAAGDdO++8o/3796tXr162owDwQ0OHDtXWrVv1/fff244CADgBY4yMcc/oBDdlyYvOXgAAAFi1b98+vf7663rmmWdUt25d23EA+JnExET17dtXjz/+uJo0aWI7DgAAZ4RiLwAAAKzq16+fJKlr166WkwDwR2+++aaSk5MVHx9vOwoAAGeMMQ4AAACw5q+//tIHH3yguLg4nXXWWbbjAPAzu3bt0qBBg/TCCy+oRo0atuMAAE7C5Cxu4aYsedHZCwAAAGu6d++uChUq6KWXXrIdBYAf6t27t4KCgtSlSxfbUQAAKBJ09gIAAMCK33//XZ9//rnef/99hYWF2Y4DwM+sX79eH330kfr166cKFSrYjgMAQJGg2AsAAAArYmJidM4556hTp062owDwQ3FxcapataqeffZZ21EAAAVgjJEx7hmd4KYseVHsBQAAQImbN2+evvvuO40ZM0aBgYG24wDwM8uXL9eXX36pYcOGqWzZsrbjAABQZJjZCwAAgBJljFGXLl100UUX6f7777cdB4Afio6OVpMmTfToo4/ajgIAQJGisxcAAAAlavLkyVq0aJFmzJghj4feAwAla+bMmZo1a5YmTpyoMmV4SwwApYXJWdzCTVny4m/XAAAAKDGZmZmKjY3VDTfcoNatW9uOA8DPeL1eRUdHq2XLlrrrrrtsxwEAoMjxMSYAAABKzKeffqo//vhDn3/+ue0oAPzQuHHj9PPPP2vevHlyHMd2HAAAihzFXgAAAJSI1NRU9ejRQ/fff78uvvhi23EA+Jn09HR17dpVt912m6688krbcQAAhWSMkTHuGZ3gpix5UewFAABAiXjvvfe0e/du9enTx3YUAH5o2LBh2rhxoyZOnGg7CgAAxYaZvQAAACh2Bw8eVP/+/dWpUyc1aNDAdhwAfiY5OVm9evXSI488ovPPP992HAAAig2d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VapUSWFhYWrbtq12795dqONQ7AUAAPBxxhhFR0frvPPOU/v27W3HgYs4wY6cDo5URzLzjMxsI1WRnMccOaH/KPauzn7ezM4p9u7U3z/vzLNPjyPn347UVDKLjcwMI5WTnEcdOVEUe/1VbGyszj77bD3xxBO2owAASinjwqUw5s6dq86dO2vRokWaOXOmMjIydOONN+rw4cO527z00kv69ttvNW7cOM2dO1c7duzQPffcU6jjlDn1JgAAACjNZsyYof/973+aPHmyAgICbMfBaXAudORceOpCqeelwvdyOJGOnHYF2PfdHunuAu6zrCPnLke6q9Bx4IPmz5+vb7/9VqNHj1ZQUJDtOAAAFKnExMR8PwcHBys4OPiY7aZNm5bv55EjR6py5cpavny5rrrqKiUkJOiTTz7R6NGjdd1110mSRowYoSZNmmjRokVq0aJFgfLQ2QsAAODDvF6vunTpolatWumOO+6wHQeAnzHGqEuXLrrwwgvVrl0723EAAChytWrVUmRkZO5X//79C/S6hIQESVLFihUlScuXL1dGRoZuuOGG3G0aN26s2rVra+HChQXOQ2cvAACADxs7dqx+++03/fjjj3IcLqH3FyYlZ9TCiXh0zJgGoDh8++23+umnnzR9+nR5PPQaAQBOnzFGxhRudEJxOppl69atioiIyF1/vK7ef/J6vXrxxRfVqlUrNW3aVJK0a9cuBQUFqXz58vm2rVKlinbt2lXgXBR7AQAAfFR6erri4uJ0xx136F//+pftOChB5ksjbT7JBuUl5yWKvSheWVlZiomJ0XXXXafWrVvbjgMAQLGIiIjIV+wtiM6dO2vlypWaP39+keeh2AsAAOCjPvzwQ/3111/65ptvbEdBCXNucqTUk2wQWGJR4Mc+++wzrV69WiNHjuTKAgAAcjz77LOaMmWK5s2bp5o1a+aur1q1qtLT03Xo0KF83b27d+9W1apVC7x/ir0AAAA+KCkpSb1791b79u1zLw2D/3CqU1iDXampqerevbvuu+8+XXrppbbjAAB8gMlZ3KKwWYwxeu655zRx4kTNmTNHdevWzff8xRdfrMDAQP3www9q27atJGnNmjXasmWLWrZsWeDjUOwFAADwQYMGDVJiYqJ69uxpOwoAP/TBBx9o586d6tu3r+0oAAC4QufOnTV69GhNnjxZ4eHhuXN4IyMjVbZs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Ro0atiMBx7Vz50699dZbevHFF/n3FABQ6hljZIx7Rie4KUteHtsBAAAAfFX37t1VoUIFvfTSS7ajoIjMmzdP1157rerWrat58+ZRQIOr9e7dW8HBwerSpYvtKAAAoIRQ7AUAACgGK1as0BdffKHu3bsrNDTUdhwUge+++0433XSTLrnkEs2aNUuVKlWyHQk4oXXr1umjjz5STEyMypcvbzsOAAAoIRR7AQAAikFMTIzq1aunTp062Y6CIjBmzBi1adNGN998s7777juFh4fbjgScVFxcnKpVq6Znn33WdhQAAIqEceHiRszsBQAAKGJz587V1KlTNXbsWAUGBtqOgzM0dOhQde7cWe3bt9ewYcNUpgx/hYa7LVu2TF999ZU++eQTlS1b1nYcAABQgujsBQAAKELGGHXp0kUXX3yx7rvvPttxcAaMMerXr5+eeeYZPf/88xo+fDiFXpQK0dHROvfcc9W+fXvbUQAAQAnjb6sAAABFaNKkSVq8eLFmzpwpj4fP1UsrY4xee+01vfnmm+rVq5fi4uLkOI7tWMApzZw5Uz/88IMmTZrEhxMAAJ9jjDtHJ7gJ//cHAAAoIpmZmYqNjVXr1q11ww032I6D05SVlaUnn3xSn3zyid59910999xztiMBBeL1etWlSxddccUVuvPOO23HAQAAFlDsBQAAKCIjR47Un3/+qVGjRtmOgtOUlpamhx9+WJMmTdLnn3+uf//737YjAQX21Vdf6ZdfftGPP/5IJzoAAH6KYi8AAEARSElJUXx8vNq1a6eLLrrIdhychuTkZN1zzz2aN2+eJkyYQGckSpX09HR17dpVt99+u/71r3/ZjgMAQJEzOYtbuClLXhR7AQAAisB7772n3bt3q0+fPraj4DQcOHBAt912m1auXKnvv/9e1157re1IQKF8/PHH2rRpkyZPnmw7CgAAsIhiLwAAwBk6cOCABgwYoCeeeEL169e3HQeFtHPnTt14443auXOn/ve//+mSSy6xHQkolOTkZPXq1Uvt27dX06ZNbccBAAAWUewFAAA4QwMGDFBGRoa6detmOwoKadOmTWrdurWOHDmiH3/8UU2aNLEdCSi0t956SwkJCerVq5ftKAAAFBtjjIxxz+gEN2XJy2M7AAAAQGm2bds2vffee3r55ZdVtWpV23FQCKtWrVKrVq3kOI4WLFhAoRel0t69e/XGG2+oc+fOql27tu04AADAMoq9AAAAZyA+Pl5hYWH673//azsKCmHx4sW66qqrVLlyZc2fP1916tSxHQk4LX369JHH41FsbKztKAAAwAUo9gIAAJym1atXa8SIEYqLi1NERITtOCigH374Qddff72aNGmiOXPmqEqVKrYjAadl06ZNGjp0qLp06aJKlSrZjgMAQLEyLlzciGIvAADAaeratatq166tp556ynYUFNDEiRN166236sorr9T06dNVvnx525GA09atWzdVqlRJL7zwgu0oAADAJbhBGwAAwGn46aefNGnSJH3++ecKDg62HQcFMHLkSHXs2FH33nuvPv/8cwUFBdmOBJy23377TaNHj9bQoUMVGhpqOw4AAHAJir0AAACFZIxRdHS0mjVrpoceesh2HBTAO++8o5deeklPPPGEhgwZooCAANuRgDMSExOj+vXr6/HHH7cdBQCAEmGMkTHuGZ3gpix5UewFAAAopKlTp+rHH3/U1KlT5fEwFcvNjDGKj49Xr1691KVLF/Xv31+O49iOBZyROXPm6Pvvv9e4ceMUGBhoOw4AAHARx7i1DA0AAOBCWVlZat68uSpVqqT//e9/FA5dzOv16sUXX9R7772nAQMGqEuXLrYjAWfMGKMWLVrIGKPFixdzDgIA+LzExERFRkaq41cdFVTOPWO40lPS9cn9nyghIcFVN2umsxcAAKAQRo0apZUrV2rhwoUUWVwsIyNDjz/+uEaNGqUPP/xQTzzxhO1IQJGYMGGClixZoh9++IFzEADAr5icxS3clCUvir0AAAAFdOTIEXXr1k333HOPWrRoYTsOTiA1NVXt2rXTtGnTNGbMGLVr1852JKBIZGZmqmvXrrrpppt03XXX2Y4DAABciGIvAABAAQ0dOlTbtm3T9OnTbUfBCSQmJurOO+/UkiVL9M033+jmm2+2HQkoMiNGjNCaNWs0ZswY21EAAIBLUewFAAAogISEBPXp00cdO3ZU48aNbcfBcezbt08333yz1q9fr5kzZ6pVq1a2IwFFJiUlRT169NBDDz2kCy+80HYcAABKnDFGbrr1mJuy5EWxFwAAoADeeOON3GIL3Gfbtm1q3bq1Dhw4oLlz5+qCCy6wHQkoUu+++6727dun3v/f3n3HVVn+fxx/HzaioLhQM0eOXKk5ytTUEnffsuEqZ/mrTNvlzIHb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AAAgB6PYCwAAHJphGOrTp48qV66sl156yew4Dmfy5Mnq2bOnOnfurK+++kpubrw9BDKqX79+Kl26tLp37252FAAAnJZhGDIM67ROsFKW1Hg3DwAAHNqvv/6q4OBgLV26VK6urmbHcRiGYWj48OH66KOP9Pbbb2vcuHFycaHDF5BRGzZs0M8//6w5c+bI3d3d7DgAACCHsxlWLUMDAADcht1u18MPP6w8efJo/fr1stlsZkdyCIZh6P3339cnn3yioUOHqn///rx2wF0wDEN169ZVbGystm7dygUTAACyQGRkpPz8/PTsd8/KPZd1LqzGR8frh84/KCIiQr6+vmbHScHKXgAA4LDmzJmjP//8U6GhoRQr71BCQoL+7//+T99++60mTpyonj17mh0JcFhLly7Vxo0btXLlSgq9AABkMePawyqslCU1ir0AAMAhxcbGasCAAXr66af12GOPmR3HIcTGxqpDhw5asmSJZs6cSY9j4B4kJCSob9++aty4sQIDA82OAwAAIIliLwAAcFBffvmljh8/rmXLlpkdxSFcuXJFrVu31oYNG7R48WI99dRTZkcCHNqMGTO0b98+zZw50+woAAAAKSj2AgAAh3P58mUNGzZMXbp0UcWKFc2OY3kXL15UixYttHfvXq1YsUINGzY0OxLg0GJiYjRw4EC1bdtWNWrUMDsOAAA5gmEYstKtx6yUJTWKvQAAwOGMGzdOkZGRGjx4sNlRLO/06dNq0qSJTp8+rTVr1qhmzZpmRwIc3qRJk3T27FkNGzbM7CgAAABpUOwFAAAO5ezZsxo7dqx69eql4sWLmx3H0v755x8FBgYqLi5OGzZsUIUKFcyOBDi8S5cuaeTIkerevbvKlCljdhwAAIA0KPYCAACHMmzYMLm5ualv375mR7G0PXv2qEmTJvLx8VFISIhKlChhdiTAKYwePVqxsbEaOHCg2VEAAMhRjGsPq7BSltRczA4AAABwpw4fPqwvvvhCffr0kb+/v9lxLGvz5s16/PHHVahQIQq9QCY6efKkPv30U7377rsKCAgwOw4AAEA6FHsBAIDD+Oijj1SoUCG9+eabZkexrN9++01PPvmkKlasqODgYBUuXNjsSIDTGDx4sHx8fPTBBx+YHQUAAOCGaOMAAAAcwo4dOzRnzhxNnTpVuXLlMjuOJf3www9q3769nnjiCS1atIjX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",
+      "text/plain": [
+       "<Figure size 2000x1000 with 2 Axes>"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# # Customize the colors\n",
+    "# criterion_values = [model.get_criterion(petab_select.Criterion.AICC) for model in models]\n",
+    "# norm = matplotlib.colors.Normalize(\n",
+    "#     vmin=min(criterion_values),\n",
+    "#     vmax=max(criterion_values),\n",
+    "# )\n",
+    "# cmap = matplotlib.colors.LinearSegmentedColormap.from_list(\"\", [\"green\",\"lightgreen\"])\n",
+    "# colorbar_mappable = matplotlib.cm.ScalarMappable(norm=norm, cmap=cmap)\n",
+    "\n",
+    "petab_select.plot.graph_iteration_layers(\n",
+    "    models=models,\n",
+    "    criterion=petab_select.Criterion.AICC,\n",
+    "    labels=labels,\n",
+    "    draw_networkx_kwargs={\n",
+    "        \"arrowstyle\": \"-|>\",\n",
+    "        \"node_shape\": \"s\",\n",
+    "        \"node_size\": 2500,\n",
+    "        \"edgecolors\": \"k\",\n",
+    "    },\n",
+    "    # colorbar_mappable=colorbar_mappable,\n",
+    ");"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.12.3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/doc/examples/workflow_cli.ipynb b/doc/examples/workflow_cli.ipynb
index a2bc1c1c..46c5a516 100644
--- a/doc/examples/workflow_cli.ipynb
+++ b/doc/examples/workflow_cli.ipynb
@@ -19,17 +19,14 @@
    "outputs": [],
    "source": [
     "# Cleanup the state and candidate models output by a previous run of this notebook\n",
-    "import os\n",
+    "import shutil\n",
+    "from pathlib import Path\n",
     "\n",
-    "try:\n",
-    "    os.remove('output/state.dill')\n",
-    "except FileNotFoundError:\n",
-    "    pass\n",
-    "\n",
-    "try:\n",
-    "    os.remove('output/models.yaml')\n",
-    "except FileNotFoundError:\n",
-    "    pass"
+    "output_path = Path().resolve() / \"output_cli\"\n",
+    "output_path_str = str(output_path)\n",
+    "if output_path.exists():\n",
+    "    shutil.rmtree(output_path_str)\n",
+    "output_path.mkdir(exist_ok=False, parents=True)"
    ]
   },
   {
@@ -45,13 +42,15 @@
    "id": "427cbd52",
    "metadata": {},
    "source": [
-    "In each call to `petab_select search`, the following options are required:\n",
-    "- `-y`: The PEtab Select problem YAML file;\n",
-    "- `-s`: A file that is used to stored the state of the problem (e.g., such that models are not repeated);\n",
-    "- `-o`: A file to store the output of the model space search; and\n",
-    "- `-m`: The method used to search or identify models in the model space.\n",
+    "Iterations of model selection start and end with `start_iteration` and `end_iteration`.\n",
+    "\n",
+    "In each call to `petab_select start_iteration`, the following options are required:\n",
+    "\n",
+    "- `--problem`: The PEtab Select problem YAML file, which normally defines the method too\n",
+    "- `--state`: A file that is used to store the state of the problem (e.g., such that models are not revisited)\n",
+    "- `--output`: A file to store the models proposed by PEtab Select\n",
     "\n",
-    "Other options can be viewed with `petab_select search --help`."
+    "Other options can be viewed with `petab_select start_iteration --help`."
    ]
   },
   {
@@ -59,7 +58,7 @@
    "id": "db3c4c95",
    "metadata": {},
    "source": [
-    "In this initial call, a PEtab Select problem is used to identify possible models for selection. The brute force method is used, which normally outputs all possible models. Here, the number of models in the output is explicitly limited to `3`. Subsequent calls with the same command will output different models."
+    "In this initial call, a PEtab Select problem is used to identify possible models for selection. Instead of using the method defined in the PEtab Select problem, we use the brute force method, which normally outputs all possible models. Here, the number of models in the output is explicitly limited to `3`. Subsequent calls with the same command will output different models."
    ]
   },
   {
@@ -69,15 +68,16 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "%%bash\n",
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
     "\n",
-    "petab_select candidates \\\n",
+    "petab_select start_iteration \\\n",
     "--problem model_selection/petab_select_problem.yaml \\\n",
-    "--state output/state.dill \\\n",
-    "--output output/models_1.yaml \\\n",
+    "--state $output_path_str/state.dill \\\n",
     "--method brute_force \\\n",
-    "--relative-paths \\\n",
-    "-l 3"
+    "--limit 3 \\\n",
+    "--output-uncalibrated-models $output_path_str/uncalibrated_models_1.yaml \\\n",
+    "--relative-paths"
    ]
   },
   {
@@ -100,8 +100,8 @@
      "text": [
       "- criteria: {}\n",
       "  estimated_parameters: {}\n",
-      "  model_hash: 65f94e223024ef684fe3e1a3aa2a54cc3ffd08895fbe4539512522d49d703ceda483aff4aa207b160dc358c458b76b25d88fbd94cacfc78bd0c70f4a46a42191\n",
-      "  model_id: 65f94e223024ef684fe3e1a3aa2a54cc3ffd08895fbe4539512522d49d703ceda483aff4aa207b160dc358c458b76b25d88fbd94cacfc78bd0c70f4a46a42191\n",
+      "  model_hash: M1_0-000\n",
+      "  model_id: M1_0-000\n",
       "  model_subspace_id: M1_0\n",
       "  model_subspace_indices:\n",
       "  - 0\n",
@@ -115,8 +115,8 @@
       "  predecessor_model_hash: null\n",
       "- criteria: {}\n",
       "  estimated_parameters: {}\n",
-      "  model_hash: 112c344171a01874a0b400640c2e0f72f2924b91712966cb868bf53b6d8ce2d09bb8e56f52b5aaca506a64754629147047646ea0c0cf568d76e74df2c5e2487a\n",
-      "  model_id: 112c344171a01874a0b400640c2e0f72f2924b91712966cb868bf53b6d8ce2d09bb8e56f52b5aaca506a64754629147047646ea0c0cf568d76e74df2c5e2487a\n",
+      "  model_hash: M1_1-000\n",
+      "  model_id: M1_1-000\n",
       "  model_subspace_id: M1_1\n",
       "  model_subspace_indices:\n",
       "  - 0\n",
@@ -130,8 +130,8 @@
       "  predecessor_model_hash: null\n",
       "- criteria: {}\n",
       "  estimated_parameters: {}\n",
-      "  model_hash: df2e1cd0744275a154036b1e1b09eaa67a76f4c08615b3e36849e3eaddcb05d1ccaedb62d148abcc41579314b2e8bec2871a8f925e3d53b90c0a4c6e9ea098ab\n",
-      "  model_id: df2e1cd0744275a154036b1e1b09eaa67a76f4c08615b3e36849e3eaddcb05d1ccaedb62d148abcc41579314b2e8bec2871a8f925e3d53b90c0a4c6e9ea098ab\n",
+      "  model_hash: M1_2-000\n",
+      "  model_id: M1_2-000\n",
       "  model_subspace_id: M1_2\n",
       "  model_subspace_indices:\n",
       "  - 0\n",
@@ -148,10 +148,42 @@
     }
    ],
    "source": [
-    "with open('output/models_1.yaml') as f:\n",
+    "with open(output_path / \"uncalibrated_models_1.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "id": "bee59532-f6bd-4d9e-8a94-a9b0302efab1",
+   "metadata": {},
+   "source": [
+    "At this point, the calibration tool should calibrate the models, and save the calibration results to disk in the PEtab Select model YAML format. For this example, we have stored the results in `model_selection/calibrated_models_1.yaml`.\n",
+    "\n",
+    "Next, we finalize the iteration by calling `petab_select end_iteration`, which requires:\n",
+    "\n",
+    "- `--state`: see `start_iteration`\n",
+    "- `--output-models`: A file used to store the models from this iteration of calibration. Note that, if the user has supplied calibration results from previous model selection jobs, then this `end_iteration` output might differ from the output of the calibration tool. This `end_iteration` output should be considered the real set of models calibrated in this iteration.\n",
+    "- `--output-metadata`: A file where metadata from the iteration is stored. This includes the signal of whether model selection has terminated."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "73665662-60ea-425c-843e-24a98c64c6a6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
+    "\n",
+    "petab_select end_iteration \\\n",
+    "--state=output/state.dill \\\n",
+    "--calibrated-models=model_selection/calibrated_models_1.yaml \\\n",
+    "--output-models=$output_path_str/models_1.yaml \\\n",
+    "--output-metadata=$output_path_str/metadata.yaml \\\n",
+    "--relative-paths"
+   ]
+  },
   {
    "cell_type": "markdown",
    "id": "8db725d4",
@@ -165,12 +197,12 @@
    "id": "d8008447",
    "metadata": {},
    "source": [
-    "Between iterations, the models from the first iteration have been calibrated, and the model with the best criterion value is `M1_2`. Here, PEtab Select will identify `M1_2` as the best model from a YAML file of calibrated models. In the following iterations, a specific model will be provided."
+    "Between iterations, the models from the first iteration have been calibrated, and the model with the best criterion value is `M1_2`. In this iteration, we will switch to the forward method and manually specify `M1_2` as the predecessor model. In subsequent iterations, the predecessor model will default to the best model of the previous iteration."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "id": "703da45d",
    "metadata": {},
    "outputs": [
@@ -180,39 +212,58 @@
      "text": [
       "- criteria:\n",
       "    AIC: 180\n",
-      "  model_id: M1_0\n",
+      "  estimated_parameters: {}\n",
+      "  model_hash: M1_0-000\n",
+      "  model_id: M1_0-000\n",
+      "  model_subspace_id: M1_0\n",
+      "  model_subspace_indices:\n",
+      "  - 0\n",
+      "  - 0\n",
+      "  - 0\n",
       "  parameters:\n",
-      "    k1: 0.0\n",
-      "    k2: 0.0\n",
-      "    k3: 0.0\n",
-      "  estimated_parameters: null\n",
+      "    k1: 0\n",
+      "    k2: 0\n",
+      "    k3: 0\n",
       "  petab_yaml: ../model_selection/petab_problem.yaml\n",
+      "  predecessor_model_hash: null\n",
       "- criteria:\n",
       "    AIC: 100\n",
-      "  model_id: M1_1\n",
+      "  estimated_parameters: {}\n",
+      "  model_hash: M1_1-000\n",
+      "  model_id: M1_1-000\n",
+      "  model_subspace_id: M1_1\n",
+      "  model_subspace_indices:\n",
+      "  - 0\n",
+      "  - 0\n",
+      "  - 0\n",
       "  parameters:\n",
       "    k1: 0.2\n",
       "    k2: 0.1\n",
       "    k3: estimate\n",
-      "  estimated_parameters:\n",
-      "    k3: 0.0\n",
       "  petab_yaml: ../model_selection/petab_problem.yaml\n",
+      "  predecessor_model_hash: null\n",
       "- criteria:\n",
       "    AIC: 50\n",
-      "  model_id: M1_2\n",
+      "  estimated_parameters: {}\n",
+      "  model_hash: M1_2-000\n",
+      "  model_id: M1_2-000\n",
+      "  model_subspace_id: M1_2\n",
+      "  model_subspace_indices:\n",
+      "  - 0\n",
+      "  - 0\n",
+      "  - 0\n",
       "  parameters:\n",
       "    k1: 0.2\n",
       "    k2: estimate\n",
-      "    k3: 0.0\n",
-      "  estimated_parameters:\n",
-      "    k2: 0.05\n",
+      "    k3: 0\n",
       "  petab_yaml: ../model_selection/petab_problem.yaml\n",
+      "  predecessor_model_hash: null\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "with open('model_selection/calibrated_first_iteration.yaml') as f:\n",
+    "with open(\"model_selection/calibrated_models_1.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
@@ -221,24 +272,40 @@
    "id": "839a1b35",
    "metadata": {},
    "source": [
-    "This is used as the predecessor model for the next search, this time using the forward method. The same state file can be used in each call. If the output file already exists, it will be overwritten."
+    "To perform the method change, we: delete the current state, select and store the model `M1_2` to disk via `petab_select get_best`, customize the problem to use the predecessor model via `candidate_space_arguments`, and supply the new method to `petab_select start_iteration`."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 6,
    "id": "22dfcc1f",
    "metadata": {},
    "outputs": [],
    "source": [
-    "%%bash\n",
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
     "\n",
-    "petab_select candidates \\\n",
+    "# save the best model of the previous iteration (`M1_2`)\n",
+    "petab_select get_best \\\n",
     "--problem model_selection/petab_select_problem.yaml \\\n",
-    "--state output/state.dill \\\n",
-    "--output output/models_2.yaml \\\n",
+    "--models model_selection/calibrated_models_1.yaml \\\n",
+    "--output output_cli/predecessor_model.yaml\n",
+    "# create a copy of the original PEtab select problem and update its paths\n",
+    "cp model_selection/petab_select_problem.yaml $output_path_str/custom_problem.yaml\n",
+    "sed -i 's|- model_space.tsv|- ../model_selection/model_space.tsv|' $output_path_str/custom_problem.yaml\n",
+    "# add the predecessor model to the problem copy\n",
+    "echo \"\"\"candidate_space_arguments:\n",
+    "  predecessor_model: predecessor_model.yaml\n",
+    "\"\"\" >> $output_path_str/custom_problem.yaml\n",
+    "# remove the state from the previous iteration\n",
+    "rm $output_path_str/state.dill\n",
+    "\n",
+    "# request models with the customized problem with the predecessor model `M1_2`, using the forward method\n",
+    "petab_select start_iteration \\\n",
+    "--problem $output_path_str/custom_problem.yaml \\\n",
+    "--state $output_path_str/state.dill \\\n",
+    "--output-uncalibrated-models $output_path_str/uncalibrated_models_2.yaml \\\n",
     "--method forward \\\n",
-    "--newly-calibrated-models model_selection/calibrated_first_iteration.yaml \\\n",
     "--relative-paths"
    ]
   },
@@ -247,14 +314,14 @@
    "id": "ac1f8a5f",
    "metadata": {},
    "source": [
-    "`M1_2` has one estimated parameter, `k2` [1]. As expected, the new candidates identified with the forward method have two estimated parameters, and one of them is `k2`.\n",
+    "`M1_2` has one estimated parameter, `k2`(*). As expected, the new candidates identified with the forward method have two estimated parameters, and one of them is `k2`.\n",
     "\n",
-    "[1] There may be additional estimated parameters specified in the PEtab problem."
+    "(*) There may be additional estimated parameters specified in the PEtab problem, which are not a part of the model selection problem."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 7,
    "id": "dd2f8850",
    "metadata": {},
    "outputs": [
@@ -264,8 +331,8 @@
      "text": [
       "- criteria: {}\n",
       "  estimated_parameters: {}\n",
-      "  model_hash: 38c95dd428b3e31da6969a50db4a1ccbcefe6d8824617d27ec2360e57d55647a25f3fdd45e5f0270786698606cbe496cd94be9495986dade4d1f1d166a4bf911\n",
-      "  model_id: 38c95dd428b3e31da6969a50db4a1ccbcefe6d8824617d27ec2360e57d55647a25f3fdd45e5f0270786698606cbe496cd94be9495986dade4d1f1d166a4bf911\n",
+      "  model_hash: M1_4-000\n",
+      "  model_id: M1_4-000\n",
       "  model_subspace_id: M1_4\n",
       "  model_subspace_indices:\n",
       "  - 0\n",
@@ -276,11 +343,11 @@
       "    k2: estimate\n",
       "    k3: estimate\n",
       "  petab_yaml: ../model_selection/petab_problem.yaml\n",
-      "  predecessor_model_hash: c95cc8f9f2b945c996856212e7d49784f72cc429dbdbb9bc04adad610f1a8103a58298c714d0518f16a5a002db5a9c27847f5da6a863637178a15a15f2fc294d\n",
+      "  predecessor_model_hash: M1_2-000\n",
       "- criteria: {}\n",
       "  estimated_parameters: {}\n",
-      "  model_hash: db8700c079c8347123adc89b7f5112256c4aaebd2af0f6e32e7582f398b2c1e5e85e588cdcc56bab054c001b96a9b42b02174266927f879d7f78e8ac5d2c33e6\n",
-      "  model_id: db8700c079c8347123adc89b7f5112256c4aaebd2af0f6e32e7582f398b2c1e5e85e588cdcc56bab054c001b96a9b42b02174266927f879d7f78e8ac5d2c33e6\n",
+      "  model_hash: M1_6-000\n",
+      "  model_id: M1_6-000\n",
       "  model_subspace_id: M1_6\n",
       "  model_subspace_indices:\n",
       "  - 0\n",
@@ -291,16 +358,42 @@
       "    k2: estimate\n",
       "    k3: 0\n",
       "  petab_yaml: ../model_selection/petab_problem.yaml\n",
-      "  predecessor_model_hash: c95cc8f9f2b945c996856212e7d49784f72cc429dbdbb9bc04adad610f1a8103a58298c714d0518f16a5a002db5a9c27847f5da6a863637178a15a15f2fc294d\n",
+      "  predecessor_model_hash: M1_2-000\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "with open('output/models_2.yaml') as f:\n",
+    "with open(output_path / \"uncalibrated_models_2.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "id": "9d8a3623-80f9-4c4f-8bf6-0465d6914207",
+   "metadata": {},
+   "source": [
+    "The calibration tool does not need to calibrate every uncalibrated model. For example, the calibration tool might return all calibration results, as soon as an improvement over the previous iteration is identified. Here, we only return the results for the `M1_4` model."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "29cb0d84-4399-4e6b-895c-e92f9cc82d68",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
+    "\n",
+    "petab_select end_iteration \\\n",
+    "--state=$output_path_str/state.dill \\\n",
+    "--calibrated-models=model_selection/calibrated_M1_4.yaml \\\n",
+    "--output-models=$output_path_str/models_2.yaml \\\n",
+    "--output-metadata=$output_path_str/metadata.yaml \\\n",
+    "--relative-paths"
+   ]
+  },
   {
    "cell_type": "markdown",
    "id": "541a3e5d",
@@ -314,12 +407,12 @@
    "id": "afba880c",
    "metadata": {},
    "source": [
-    "Models `M1_4` and `M1_6` were calibrated, with `M1_4` having the superior criterion value, so is used as the predecessor model in the next forward search."
+    "The models from the previous iteration (i.e. `M1_4`) were stored in the state. Here, we perform an iteration of the forward method, which is automatically initialized with the `M1_4` model."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 9,
    "id": "54c5b027",
    "metadata": {},
    "outputs": [
@@ -328,40 +421,47 @@
      "output_type": "stream",
      "text": [
       "criteria:\n",
-      "  AIC: 30\n",
-      "model_id: M1_4_0\n",
-      "parameters:\n",
-      "  k1: 0.2\n",
-      "  k2: estimate\n",
-      "  k3: estimate\n",
+      "  AIC: 15\n",
       "estimated_parameters:\n",
       "  k2: 0.15\n",
       "  k3: 0.0\n",
+      "model_hash: M1_4-000\n",
+      "model_id: M1_4-000\n",
+      "model_subspace_id: M1_4\n",
+      "model_subspace_indices:\n",
+      "- 0\n",
+      "- 0\n",
+      "- 0\n",
+      "parameters:\n",
+      "  k1: 0\n",
+      "  k2: estimate\n",
+      "  k3: estimate\n",
       "petab_yaml: ../model_selection/petab_problem.yaml\n",
+      "predecessor_model_hash: null\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "with open('model_selection/calibrated_M1_4.yaml') as f:\n",
+    "with open(\"model_selection/calibrated_M1_4.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 10,
    "id": "818e59e4",
    "metadata": {},
    "outputs": [],
    "source": [
-    "%%bash\n",
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
     "\n",
-    "petab_select candidates \\\n",
-    "--problem model_selection/petab_select_problem.yaml \\\n",
-    "--state output/state.dill \\\n",
-    "--output output/models_3.yaml \\\n",
+    "petab_select start_iteration \\\n",
+    "--problem $output_path_str/custom_problem.yaml \\\n",
+    "--state $output_path_str/state.dill \\\n",
+    "--output-uncalibrated-models $output_path_str/uncalibrated_models_3.yaml \\\n",
     "--method forward \\\n",
-    "--newly-calibrated-models model_selection/calibrated_M1_4.yaml \\\n",
     "--relative-paths"
    ]
   },
@@ -370,12 +470,12 @@
    "id": "889dedc1",
    "metadata": {},
    "source": [
-    "The model space contains only one model with 3 or more estimated parameters, which is a valid candidate model in this case."
+    "As we are performing a forward search from `M1_4`, which has two parameters, then all models in this iteration with have 3+ parameters. This model space contains only one model with 3 or more estimated parameters. We finalize the iteration with its calibration results."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 11,
    "id": "9f393030",
    "metadata": {},
    "outputs": [
@@ -385,8 +485,8 @@
      "text": [
       "- criteria: {}\n",
       "  estimated_parameters: {}\n",
-      "  model_hash: 7c105406ec11716473939a0bbb5281066c1014b54e2480ba126030f5c18a597a27a2ca9247aa60d8262f488165079d1c9e040f9d712ec4e19c2d2122a586f3e5\n",
-      "  model_id: 7c105406ec11716473939a0bbb5281066c1014b54e2480ba126030f5c18a597a27a2ca9247aa60d8262f488165079d1c9e040f9d712ec4e19c2d2122a586f3e5\n",
+      "  model_hash: M1_7-000\n",
+      "  model_id: M1_7-000\n",
       "  model_subspace_id: M1_7\n",
       "  model_subspace_indices:\n",
       "  - 0\n",
@@ -397,28 +497,46 @@
       "    k2: estimate\n",
       "    k3: estimate\n",
       "  petab_yaml: ../model_selection/petab_problem.yaml\n",
-      "  predecessor_model_hash: 0e0f00ee37d03db0f6ad7bb9cb92eb4ae3aef85fa1e1d9d38dfc5954fbcf62e8768803bedd42fb3638b681d4840e352e5bfc29a1800247ebeaddfbdb4baca46b\n",
+      "  predecessor_model_hash: M1_4-000\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "with open('output/models_3.yaml') as f:\n",
+    "with open(output_path / \"uncalibrated_models_3.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "id": "a4084bd1-5bd7-4e12-8146-67137da4909a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
+    "\n",
+    "petab_select end_iteration \\\n",
+    "--state=$output_path_str/state.dill \\\n",
+    "--calibrated-models=model_selection/calibrated_M1_7.yaml \\\n",
+    "--output-models=$output_path_str/models_3.yaml \\\n",
+    "--output-metadata=$output_path_str/metadata.yaml \\\n",
+    "--relative-paths"
+   ]
+  },
   {
    "cell_type": "markdown",
    "id": "de959e53",
    "metadata": {},
    "source": [
     "## Fourth iteration\n",
-    "As there are no models in the model space with additional estimated parameters, subsequent forward searches will return no candidate models."
+    "As there are no models in the model space with 4+ parameters, subsequent forward searches will return no candidate models. This can be used by tools to detect when model selection terminates."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 13,
    "id": "9ef2fe2f",
    "metadata": {},
    "outputs": [
@@ -428,47 +546,54 @@
      "text": [
       "criteria:\n",
       "  AIC: 20\n",
-      "model_id: M1_7_0\n",
-      "parameters:\n",
-      "  k1: estimate\n",
-      "  k2: estimate\n",
-      "  k3: estimate\n",
       "estimated_parameters:\n",
       "  k1: 0.25\n",
       "  k2: 0.1\n",
       "  k3: 0.0\n",
+      "model_hash: M1_7-000\n",
+      "model_id: M1_7-000\n",
+      "model_subspace_id: M1_7\n",
+      "model_subspace_indices:\n",
+      "- 0\n",
+      "- 0\n",
+      "- 0\n",
+      "parameters:\n",
+      "  k1: estimate\n",
+      "  k2: estimate\n",
+      "  k3: estimate\n",
       "petab_yaml: ../model_selection/petab_problem.yaml\n",
+      "predecessor_model_hash: null\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "with open('model_selection/calibrated_M1_7.yaml') as f:\n",
+    "with open(\"model_selection/calibrated_M1_7.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
-   "id": "5afa0d08",
+   "execution_count": 14,
+   "id": "35ed7ceb-6783-4956-9951-dbc55bfa9239",
    "metadata": {},
    "outputs": [],
    "source": [
-    "%%bash\n",
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
     "\n",
-    "petab_select candidates \\\n",
+    "petab_select start_iteration \\\n",
     "--problem model_selection/petab_select_problem.yaml \\\n",
-    "--state output/state.dill \\\n",
-    "--output output/models_4.yaml \\\n",
+    "--state $output_path_str/state.dill \\\n",
+    "--output-uncalibrated-models $output_path_str/uncalibrated_models_4.yaml \\\n",
     "--method forward \\\n",
-    "--newly-calibrated-models model_selection/calibrated_M1_7.yaml \\\n",
     "--relative-paths"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
-   "id": "97cdb98d",
+   "execution_count": 15,
+   "id": "5fe1e848-e112-4ad2-ae09-57cdb7506ff8",
    "metadata": {},
    "outputs": [
     {
@@ -482,7 +607,7 @@
     }
    ],
    "source": [
-    "with open('output/models_4.yaml') as f:\n",
+    "with open(output_path / \"uncalibrated_models_4.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
@@ -492,33 +617,33 @@
    "metadata": {},
    "source": [
     "## Fifth iteration\n",
-    "Although no additional models are found with a forward search initialized at the best model so far (`M1_7`), there are additional models in the model space that are yet to be calibrated, which can be identified with the brute force method."
+    "Although no additional models are found with a forward search initialized at the best model so far (`M1_7`), there are additional models in the model space that were not calibrated, which can be identified by using the brute force method with exclusions for the calibrated models."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 16,
    "id": "d5b5087d",
    "metadata": {},
    "outputs": [],
    "source": [
-    "%%bash\n",
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
     "\n",
-    "petab_select candidates \\\n",
+    "petab_select start_iteration \\\n",
     "--problem model_selection/petab_select_problem.yaml \\\n",
-    "--state output/state.dill \\\n",
-    "--output output/models.yaml \\\n",
+    "--state $output_path_str/state_5.dill \\\n",
+    "--output-uncalibrated-models $output_path_str/uncalibrated_models_5.yaml \\\n",
     "--method brute_force \\\n",
-    "--excluded-models output/models_1.yaml \\\n",
-    "--excluded-models output/models_2.yaml \\\n",
-    "--excluded-models output/models_3.yaml \\\n",
-    "--excluded-models output/models_4.yaml \\\n",
+    "--excluded-models $output_path_str/models_1.yaml \\\n",
+    "--excluded-models $output_path_str/models_2.yaml \\\n",
+    "--excluded-models $output_path_str/models_3.yaml \\\n",
     "--relative-paths"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 17,
    "id": "30721bfa",
    "metadata": {},
    "outputs": [
@@ -528,8 +653,8 @@
      "text": [
       "- criteria: {}\n",
       "  estimated_parameters: {}\n",
-      "  model_hash: b7584bfd6f35206dfe32fa0143e53cea808faf965e0c0547bf6ee1cdce7a75cd3ff0aa2bcb1faa27625166454f83e3fcac52cdf43b28e8186fff9a01ac3f8006\n",
-      "  model_id: b7584bfd6f35206dfe32fa0143e53cea808faf965e0c0547bf6ee1cdce7a75cd3ff0aa2bcb1faa27625166454f83e3fcac52cdf43b28e8186fff9a01ac3f8006\n",
+      "  model_hash: M1_3-000\n",
+      "  model_id: M1_3-000\n",
       "  model_subspace_id: M1_3\n",
       "  model_subspace_indices:\n",
       "  - 0\n",
@@ -543,8 +668,8 @@
       "  predecessor_model_hash: null\n",
       "- criteria: {}\n",
       "  estimated_parameters: {}\n",
-      "  model_hash: de4a2f17d8b0228a31d7451631cf3662d0ecf4dc7738ab6ca3d1de65e817844c9c1df806ec9daf81644b9c10f00185dc8c8de880d9db23a98acadb817f5d481c\n",
-      "  model_id: de4a2f17d8b0228a31d7451631cf3662d0ecf4dc7738ab6ca3d1de65e817844c9c1df806ec9daf81644b9c10f00185dc8c8de880d9db23a98acadb817f5d481c\n",
+      "  model_hash: M1_5-000\n",
+      "  model_id: M1_5-000\n",
       "  model_subspace_id: M1_5\n",
       "  model_subspace_indices:\n",
       "  - 0\n",
@@ -556,12 +681,27 @@
       "    k3: estimate\n",
       "  petab_yaml: ../model_selection/petab_problem.yaml\n",
       "  predecessor_model_hash: null\n",
+      "- criteria: {}\n",
+      "  estimated_parameters: {}\n",
+      "  model_hash: M1_6-000\n",
+      "  model_id: M1_6-000\n",
+      "  model_subspace_id: M1_6\n",
+      "  model_subspace_indices:\n",
+      "  - 0\n",
+      "  - 0\n",
+      "  - 0\n",
+      "  parameters:\n",
+      "    k1: estimate\n",
+      "    k2: estimate\n",
+      "    k3: 0\n",
+      "  petab_yaml: ../model_selection/petab_problem.yaml\n",
+      "  predecessor_model_hash: null\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "with open('output/models.yaml') as f:\n",
+    "with open(output_path / \"uncalibrated_models_5.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
@@ -570,32 +710,33 @@
    "id": "655f8efc",
    "metadata": {},
    "source": [
-    "# Post-processing\n",
+    "## Post-processing\n",
     "After the selection algorithm has terminated, the best model can be stored separately by supplying a list of calibrated models."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 18,
    "id": "73d54111",
    "metadata": {},
    "outputs": [],
    "source": [
-    "%%bash\n",
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
     "\n",
-    "petab_select best \\\n",
+    "petab_select get_best \\\n",
     "--problem model_selection/petab_select_problem.yaml \\\n",
-    "--models model_selection/calibrated_first_iteration.yaml \\\n",
-    "--models model_selection/calibrated_M1_4.yaml \\\n",
-    "--models model_selection/calibrated_M1_7.yaml \\\n",
-    "--output output/best_model.yaml \\\n",
-    "--state output/state.dill \\\n",
+    "--models $output_path_str/models_1.yaml \\\n",
+    "--models $output_path_str/models_2.yaml \\\n",
+    "--models $output_path_str/models_3.yaml \\\n",
+    "--output $output_path_str/best_model.yaml \\\n",
+    "--state $output_path_str/state.dill \\\n",
     "--relative-paths"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
+   "execution_count": 19,
    "id": "c36564f1",
    "metadata": {},
    "outputs": [
@@ -604,17 +745,19 @@
      "output_type": "stream",
      "text": [
       "criteria:\n",
-      "  AIC: 20\n",
+      "  AIC: 15.0\n",
       "estimated_parameters:\n",
-      "  k1: 0.25\n",
-      "  k2: 0.1\n",
+      "  k2: 0.15\n",
       "  k3: 0.0\n",
-      "model_hash: null\n",
-      "model_id: M1_7_0\n",
-      "model_subspace_id: null\n",
-      "model_subspace_indices: null\n",
+      "model_hash: M1_4-000\n",
+      "model_id: M1_4-000\n",
+      "model_subspace_id: M1_4\n",
+      "model_subspace_indices:\n",
+      "- 0\n",
+      "- 0\n",
+      "- 0\n",
       "parameters:\n",
-      "  k1: estimate\n",
+      "  k1: 0\n",
       "  k2: estimate\n",
       "  k3: estimate\n",
       "petab_yaml: ../model_selection/petab_problem.yaml\n",
@@ -624,7 +767,7 @@
     }
    ],
    "source": [
-    "with open('output/best_model.yaml') as f:\n",
+    "with open(output_path / \"best_model.yaml\") as f:\n",
     "    print(f.read())"
    ]
   },
@@ -638,7 +781,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 20,
    "id": "d5d03cd6",
    "metadata": {},
    "outputs": [
@@ -646,22 +789,23 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "output/best_model_petab/problem.yaml\n"
+      "output_cli/best_model_petab/problem.yaml\n"
      ]
     }
    ],
    "source": [
-    "%%bash\n",
+    "%%bash -s \"$output_path_str\"\n",
+    "output_path_str=$1\n",
     "\n",
     "petab_select model_to_petab \\\n",
-    "--model output/best_model.yaml \\\n",
-    "--output output/best_model_petab"
+    "--model $output_path_str/best_model.yaml \\\n",
+    "--output $output_path_str/best_model_petab"
    ]
   }
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -675,7 +819,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.10"
+   "version": "3.12.3"
   }
  },
  "nbformat": 4,
diff --git a/doc/examples/workflow_python.ipynb b/doc/examples/workflow_python.ipynb
index 361f12a6..2a203987 100644
--- a/doc/examples/workflow_python.ipynb
+++ b/doc/examples/workflow_python.ipynb
@@ -36,32 +36,40 @@
      "output_type": "stream",
      "text": [
       "Information about the model selection problem.\n",
-      "\n",
-      "YAML path: model_selection/petab_select_problem.yaml\n",
+      "YAML: model_selection/petab_select_problem.yaml\n",
       "Method: forward\n",
-      "Criterion: AIC\n",
+      "Criterion: Criterion.AIC\n",
+      "Version: beta_1\n",
       "\n"
      ]
     }
    ],
    "source": [
     "import petab_select\n",
-    "from petab_select import ForwardCandidateSpace, Model\n",
+    "from petab_select import Model\n",
+    "from petab_select.constants import (\n",
+    "    CANDIDATE_SPACE,\n",
+    "    MODELS,\n",
+    "    UNCALIBRATED_MODELS,\n",
+    ")\n",
+    "\n",
+    "BOLD_TEXT = \"\\033[1m\"\n",
+    "NORMAL_TEXT = \"\\033[0m\"\n",
     "\n",
     "# Load the PEtab Select problem.\n",
     "select_problem = petab_select.Problem.from_yaml(\n",
-    "    'model_selection/petab_select_problem.yaml'\n",
+    "    \"model_selection/petab_select_problem.yaml\"\n",
     ")\n",
     "# Fake criterion values as a surrogate for a model calibration tool.\n",
     "fake_criterion = {\n",
-    "    'M1_0': 200,\n",
-    "    'M1_1': 150,\n",
-    "    'M1_2': 140,\n",
-    "    'M1_3': 130,\n",
-    "    'M1_4': -40,\n",
-    "    'M1_5': -70,\n",
-    "    'M1_6': -110,\n",
-    "    'M1_7': 50,\n",
+    "    \"M1_0\": 200,\n",
+    "    \"M1_1\": 150,\n",
+    "    \"M1_2\": 140,\n",
+    "    \"M1_3\": 130,\n",
+    "    \"M1_4\": -40,\n",
+    "    \"M1_5\": -70,\n",
+    "    \"M1_6\": -110,\n",
+    "    \"M1_7\": 50,\n",
     "}\n",
     "\n",
     "\n",
@@ -90,18 +98,8 @@
     "    )\n",
     "\n",
     "\n",
-    "# Calibrated and newly calibrated models should be tracked between iterations.\n",
-    "calibrated_models = {}\n",
-    "newly_calibrated_models = {}\n",
-    "\n",
-    "print(\n",
-    "    f\"\"\"Information about the model selection problem.\n",
-    "\n",
-    "YAML path: {select_problem.yaml_path}\n",
-    "Method: {select_problem.method}\n",
-    "Criterion: {select_problem.criterion}\n",
-    "\"\"\"\n",
-    ")"
+    "print(\"Information about the model selection problem:\")\n",
+    "print(select_problem)"
    ]
   },
   {
@@ -111,7 +109,7 @@
    "source": [
     "## First iteration\n",
     "\n",
-    "Neighbors of the initial model in the model space are identified for testing. Here, no initial model is specified. If an initial model is required for the algorithm, PEtab Select can automatically use a virtual initial model, if such a model is defined. For example, for the forward and backward methods, the virtual initial model defaults to a model with no parameters estimated, and all parameters estimated, respectively."
+    "Neighbors of the initial predecessor model in the model space are identified for testing. Here, no initial predecessor model is specified. If an initial predecessor model is required for the algorithm, PEtab Select can automatically use the `VIRTUAL_INITIAL_MODEL`. With the forward and backward methods, the virtual initial model defaults to a model with no parameters estimated, and all parameters estimated, respectively."
    ]
   },
   {
@@ -119,14 +117,15 @@
    "id": "080d08d4",
    "metadata": {},
    "source": [
-    "The model candidate space is setup with the initial model. The model space is then used to find neighbors to the initial model. The candidate space is used to calculate distances between models, and whether a candidate model represents a valid move in model space.\n",
+    "The model space is then used to find neighbors to the initial model. A candidate space is used to calculate distances between models, and whether a candidate model represents a valid move in model space.\n",
+    "\n",
+    "The built-in `ForwardCandidateSpace` uses the following properties to identify candidate models:\n",
     "\n",
-    "The in-built `ForwardCandidateSpace` uses the following properties to identify candidate models:\n",
-    "- previously estimated parameters must not be fixed;\n",
+    "- previously estimated parameters must remain estimated;\n",
     "- the number of estimated parameters must increase; and\n",
-    "- the increase in the number of estimated parameters must be minimal.\n",
+    "- this increase must be minimal.\n",
     "\n",
-    "The model space keeps a history of identified neighbors, such that subsequent calls ignore previously identified neighbors. This can be disabled by changing usage to `petab_select.ModelSpace.search(..., exclude=False)`, or reset to forget all history with `petab_select.ModelSpace.reset()`."
+    "The model space keeps a history of calibrated models, such that subsequent calls ignore previously identified neighbors. This can be disabled by changing usage to `petab_select.ModelSpace.search(..., exclude=False)`, or reset to forget all history with `petab_select.ModelSpace.reset()`."
    ]
   },
   {
@@ -136,7 +135,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "candidate_space = petab_select.ui.candidates(problem=select_problem)"
+    "iteration = petab_select.ui.start_iteration(problem=select_problem)"
    ]
   },
   {
@@ -144,9 +143,9 @@
    "id": "b9245832",
    "metadata": {},
    "source": [
-    "Model IDs default to the model hash, which is generated from hashing the model subspace ID and model parameterization.\n",
+    "Model IDs default to the model hash, which is generated from the model subspace ID, the model parameterization, and the \"PEtab hash\". The PEtab hash is generated from the location of the PEtab problem YAML file, and the nominal values and list of estimated parameters from the model's PEtab parameter table.\n",
     "\n",
-    "Here, the model identified is a model with all possible parameters fixed. This is because the default virtual initial model is the same parameterization, and the closest model in the \"real\" model subspace is the same parameterization. If the initial model was from the \"real\" model subspace, then candidate models would be true forward steps in the subspace (e.g. an increase in the number of estimated parameters)."
+    "Here, the model identified is a model with all possible parameters fixed, because it matches the virtual initial model. If the initial model was from the \"real\" model subspace, then candidate models would be true forward steps in the subspace (e.g. an increase in the number of estimated parameters)."
    ]
   },
   {
@@ -172,15 +171,15 @@
       "Model subspace ID: M1_0\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 0, 'k2': 0, 'k3': 0}\n",
-      "Model hash: 65f94e223024ef684fe3e1a3aa2a54cc3ffd08895fbe4539512522d49d703ceda483aff4aa207b160dc358c458b76b25d88fbd94cacfc78bd0c70f4a46a42191\n",
-      "Model ID: 65f94e223024ef684fe3e1a3aa2a54cc3ffd08895fbe4539512522d49d703ceda483aff4aa207b160dc358c458b76b25d88fbd94cacfc78bd0c70f4a46a42191\n",
-      "AIC: None\n",
+      "Model hash: M1_0-000\n",
+      "Model ID: M1_0-000\n",
+      "Criterion.AIC: None\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "for candidate_model in candidate_space.models:\n",
+    "for candidate_model in iteration[UNCALIBRATED_MODELS]:\n",
     "    print_model(candidate_model)"
    ]
   },
@@ -200,15 +199,13 @@
    "outputs": [],
    "source": [
     "# Set fake criterion values that might be the output of a model calibration tool.\n",
-    "for candidate_model in candidate_space.models:\n",
+    "for candidate_model in iteration[UNCALIBRATED_MODELS]:\n",
     "    calibrate(candidate_model)\n",
     "\n",
-    "newly_calibrated_models = {\n",
-    "    model.get_hash(): model for model in candidate_space.models\n",
-    "}\n",
-    "calibrated_models.update(newly_calibrated_models)\n",
-    "\n",
-    "select_problem.exclude_models(newly_calibrated_models.values())"
+    "iteration_results = petab_select.ui.end_iteration(\n",
+    "    candidate_space=iteration[CANDIDATE_SPACE],\n",
+    "    calibrated_models=iteration[UNCALIBRATED_MODELS],\n",
+    ")"
    ]
   },
   {
@@ -224,15 +221,17 @@
       "Model subspace ID: M1_0\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 0, 'k2': 0, 'k3': 0}\n",
-      "Model hash: 65f94e223024ef684fe3e1a3aa2a54cc3ffd08895fbe4539512522d49d703ceda483aff4aa207b160dc358c458b76b25d88fbd94cacfc78bd0c70f4a46a42191\n",
-      "Model ID: 65f94e223024ef684fe3e1a3aa2a54cc3ffd08895fbe4539512522d49d703ceda483aff4aa207b160dc358c458b76b25d88fbd94cacfc78bd0c70f4a46a42191\n",
-      "AIC: 200\n",
+      "Model hash: M1_0-000\n",
+      "Model ID: M1_0-000\n",
+      "Criterion.AIC: 200\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "local_best_model = select_problem.get_best(newly_calibrated_models.values())\n",
+    "local_best_model = petab_select.ui.get_best(\n",
+    "    problem=select_problem, models=iteration_results[MODELS].values()\n",
+    ")\n",
     "print_model(local_best_model)"
    ]
   },
@@ -250,7 +249,7 @@
    "id": "d5b167b4",
    "metadata": {},
    "source": [
-    "The chosen model is used as the predecessor model, such that neighboring models are identified with respect to the chosen model."
+    "The chosen model is used as the predecessor model, such that neighboring models are identified with respect to the chosen model. Here, we define a dummy calibration tool that performs all parts of the model selection iteration."
    ]
   },
   {
@@ -260,11 +259,27 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "petab_select.ui.candidates(\n",
-    "    problem=select_problem,\n",
-    "    candidate_space=candidate_space,\n",
-    "    newly_calibrated_models=newly_calibrated_models,\n",
-    ");"
+    "def dummy_calibration_tool(\n",
+    "    problem: petab_select.Problem,\n",
+    "    candidate_space: petab_select.CandidateSpace = None,\n",
+    "):\n",
+    "    # Initialize iteration\n",
+    "    iteration = petab_select.ui.start_iteration(\n",
+    "        problem=problem,\n",
+    "        candidate_space=candidate_space,\n",
+    "    )\n",
+    "\n",
+    "    # \"Calibrate\": set fake criterion values that might be the output of a model calibration tool.\n",
+    "    for candidate_model in iteration[UNCALIBRATED_MODELS]:\n",
+    "        calibrate(candidate_model)\n",
+    "\n",
+    "    # Finalize iteration\n",
+    "    iteration_results = petab_select.ui.end_iteration(\n",
+    "        candidate_space=iteration[CANDIDATE_SPACE],\n",
+    "        calibrated_models=iteration[UNCALIBRATED_MODELS],\n",
+    "    )\n",
+    "\n",
+    "    return iteration_results"
    ]
   },
   {
@@ -280,74 +295,40 @@
       "Model subspace ID: M1_1\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 0.2, 'k2': 0.1, 'k3': 'estimate'}\n",
-      "Model hash: 112c344171a01874a0b400640c2e0f72f2924b91712966cb868bf53b6d8ce2d09bb8e56f52b5aaca506a64754629147047646ea0c0cf568d76e74df2c5e2487a\n",
-      "Model ID: 112c344171a01874a0b400640c2e0f72f2924b91712966cb868bf53b6d8ce2d09bb8e56f52b5aaca506a64754629147047646ea0c0cf568d76e74df2c5e2487a\n",
-      "AIC: None\n",
+      "Model hash: M1_1-000\n",
+      "Model ID: M1_1-000\n",
+      "Criterion.AIC: 150\n",
       "\n",
       "Model subspace ID: M1_2\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 0.2, 'k2': 'estimate', 'k3': 0}\n",
-      "Model hash: df2e1cd0744275a154036b1e1b09eaa67a76f4c08615b3e36849e3eaddcb05d1ccaedb62d148abcc41579314b2e8bec2871a8f925e3d53b90c0a4c6e9ea098ab\n",
-      "Model ID: df2e1cd0744275a154036b1e1b09eaa67a76f4c08615b3e36849e3eaddcb05d1ccaedb62d148abcc41579314b2e8bec2871a8f925e3d53b90c0a4c6e9ea098ab\n",
-      "AIC: None\n",
+      "Model hash: M1_2-000\n",
+      "Model ID: M1_2-000\n",
+      "Criterion.AIC: 140\n",
       "\n",
+      "\u001B[1mBEST MODEL OF CURRENT ITERATION\u001B[0m\n",
       "Model subspace ID: M1_3\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 'estimate', 'k2': 0.1, 'k3': 0}\n",
-      "Model hash: b7584bfd6f35206dfe32fa0143e53cea808faf965e0c0547bf6ee1cdce7a75cd3ff0aa2bcb1faa27625166454f83e3fcac52cdf43b28e8186fff9a01ac3f8006\n",
-      "Model ID: b7584bfd6f35206dfe32fa0143e53cea808faf965e0c0547bf6ee1cdce7a75cd3ff0aa2bcb1faa27625166454f83e3fcac52cdf43b28e8186fff9a01ac3f8006\n",
-      "AIC: None\n",
+      "Model hash: M1_3-000\n",
+      "Model ID: M1_3-000\n",
+      "Criterion.AIC: 130\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "for candidate_model in candidate_space.models:\n",
-    "    print_model(candidate_model)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 8,
-   "id": "51c54e81",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# Set fake criterion values that might be the output of a model calibration tool.\n",
-    "for candidate_model in candidate_space.models:\n",
-    "    calibrate(candidate_model)\n",
-    "\n",
-    "newly_calibrated_models = {\n",
-    "    model.get_hash(): model for model in candidate_space.models\n",
-    "}\n",
-    "calibrated_models.update(newly_calibrated_models)\n",
+    "iteration_results = dummy_calibration_tool(\n",
+    "    problem=select_problem, candidate_space=iteration_results[CANDIDATE_SPACE]\n",
+    ")\n",
+    "local_best_model = petab_select.ui.get_best(\n",
+    "    problem=select_problem, models=iteration_results[MODELS].values()\n",
+    ")\n",
     "\n",
-    "select_problem.exclude_models(newly_calibrated_models.values())"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 9,
-   "id": "69185082",
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Model subspace ID: M1_3\n",
-      "PEtab YAML location: model_selection/petab_problem.yaml\n",
-      "Custom model parameters: {'k1': 'estimate', 'k2': 0.1, 'k3': 0}\n",
-      "Model hash: b7584bfd6f35206dfe32fa0143e53cea808faf965e0c0547bf6ee1cdce7a75cd3ff0aa2bcb1faa27625166454f83e3fcac52cdf43b28e8186fff9a01ac3f8006\n",
-      "Model ID: b7584bfd6f35206dfe32fa0143e53cea808faf965e0c0547bf6ee1cdce7a75cd3ff0aa2bcb1faa27625166454f83e3fcac52cdf43b28e8186fff9a01ac3f8006\n",
-      "AIC: 130\n",
-      "\n"
-     ]
-    }
-   ],
-   "source": [
-    "local_best_model = select_problem.get_best(newly_calibrated_models.values())\n",
-    "print_model(local_best_model)"
+    "for candidate_model in iteration_results[MODELS].values():\n",
+    "    if candidate_model.get_hash() == local_best_model.get_hash():\n",
+    "        print(BOLD_TEXT + \"BEST MODEL OF CURRENT ITERATION\" + NORMAL_TEXT)\n",
+    "    print_model(candidate_model)"
    ]
   },
   {
@@ -360,23 +341,9 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 8,
    "id": "6d3468d3",
    "metadata": {},
-   "outputs": [],
-   "source": [
-    "petab_select.ui.candidates(\n",
-    "    problem=select_problem,\n",
-    "    candidate_space=candidate_space,\n",
-    "    newly_calibrated_models=newly_calibrated_models,\n",
-    ");"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 11,
-   "id": "6db6309b",
-   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
@@ -385,67 +352,33 @@
       "Model subspace ID: M1_5\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 'estimate', 'k2': 0.1, 'k3': 'estimate'}\n",
-      "Model hash: de4a2f17d8b0228a31d7451631cf3662d0ecf4dc7738ab6ca3d1de65e817844c9c1df806ec9daf81644b9c10f00185dc8c8de880d9db23a98acadb817f5d481c\n",
-      "Model ID: de4a2f17d8b0228a31d7451631cf3662d0ecf4dc7738ab6ca3d1de65e817844c9c1df806ec9daf81644b9c10f00185dc8c8de880d9db23a98acadb817f5d481c\n",
-      "AIC: None\n",
+      "Model hash: M1_5-000\n",
+      "Model ID: M1_5-000\n",
+      "Criterion.AIC: -70\n",
       "\n",
+      "\u001B[1mBEST MODEL OF CURRENT ITERATION\u001B[0m\n",
       "Model subspace ID: M1_6\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 'estimate', 'k2': 'estimate', 'k3': 0}\n",
-      "Model hash: db8700c079c8347123adc89b7f5112256c4aaebd2af0f6e32e7582f398b2c1e5e85e588cdcc56bab054c001b96a9b42b02174266927f879d7f78e8ac5d2c33e6\n",
-      "Model ID: db8700c079c8347123adc89b7f5112256c4aaebd2af0f6e32e7582f398b2c1e5e85e588cdcc56bab054c001b96a9b42b02174266927f879d7f78e8ac5d2c33e6\n",
-      "AIC: None\n",
+      "Model hash: M1_6-000\n",
+      "Model ID: M1_6-000\n",
+      "Criterion.AIC: -110\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "for candidate_model in candidate_space.models:\n",
-    "    print_model(candidate_model)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 12,
-   "id": "26614e09",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# Set fake criterion values that might be the output of a model calibration tool.\n",
-    "for candidate_model in candidate_space.models:\n",
-    "    calibrate(candidate_model)\n",
-    "\n",
-    "newly_calibrated_models = {\n",
-    "    model.get_hash(): model for model in candidate_space.models\n",
-    "}\n",
-    "calibrated_models.update(newly_calibrated_models)\n",
+    "iteration_results = dummy_calibration_tool(\n",
+    "    problem=select_problem, candidate_space=iteration_results[CANDIDATE_SPACE]\n",
+    ")\n",
+    "local_best_model = petab_select.ui.get_best(\n",
+    "    problem=select_problem, models=iteration_results[MODELS].values()\n",
+    ")\n",
     "\n",
-    "select_problem.exclude_models(newly_calibrated_models.values())"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 13,
-   "id": "9db19881",
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Model subspace ID: M1_6\n",
-      "PEtab YAML location: model_selection/petab_problem.yaml\n",
-      "Custom model parameters: {'k1': 'estimate', 'k2': 'estimate', 'k3': 0}\n",
-      "Model hash: db8700c079c8347123adc89b7f5112256c4aaebd2af0f6e32e7582f398b2c1e5e85e588cdcc56bab054c001b96a9b42b02174266927f879d7f78e8ac5d2c33e6\n",
-      "Model ID: db8700c079c8347123adc89b7f5112256c4aaebd2af0f6e32e7582f398b2c1e5e85e588cdcc56bab054c001b96a9b42b02174266927f879d7f78e8ac5d2c33e6\n",
-      "AIC: -110\n",
-      "\n"
-     ]
-    }
-   ],
-   "source": [
-    "local_best_model = select_problem.get_best(newly_calibrated_models.values())\n",
-    "print_model(local_best_model)"
+    "for candidate_model in iteration_results[MODELS].values():\n",
+    "    if candidate_model.get_hash() == local_best_model.get_hash():\n",
+    "        print(BOLD_TEXT + \"BEST MODEL OF CURRENT ITERATION\" + NORMAL_TEXT)\n",
+    "    print_model(candidate_model)"
    ]
   },
   {
@@ -458,85 +391,37 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
+   "execution_count": 9,
    "id": "9f9c438c",
    "metadata": {},
-   "outputs": [],
-   "source": [
-    "petab_select.ui.candidates(\n",
-    "    problem=select_problem,\n",
-    "    candidate_space=candidate_space,\n",
-    "    newly_calibrated_models=newly_calibrated_models,\n",
-    ");"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 15,
-   "id": "0dfff3e1",
-   "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
+      "\u001B[1mBEST MODEL OF CURRENT ITERATION\u001B[0m\n",
       "Model subspace ID: M1_7\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 'estimate', 'k2': 'estimate', 'k3': 'estimate'}\n",
-      "Model hash: 7c105406ec11716473939a0bbb5281066c1014b54e2480ba126030f5c18a597a27a2ca9247aa60d8262f488165079d1c9e040f9d712ec4e19c2d2122a586f3e5\n",
-      "Model ID: 7c105406ec11716473939a0bbb5281066c1014b54e2480ba126030f5c18a597a27a2ca9247aa60d8262f488165079d1c9e040f9d712ec4e19c2d2122a586f3e5\n",
-      "AIC: None\n",
+      "Model hash: M1_7-000\n",
+      "Model ID: M1_7-000\n",
+      "Criterion.AIC: 50\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "for candidate_model in candidate_space.models:\n",
-    "    print_model(candidate_model)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 16,
-   "id": "7cd2181c",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# Set fake criterion values that might be the output of a model calibration tool.\n",
-    "for candidate_model in candidate_space.models:\n",
-    "    calibrate(candidate_model)\n",
-    "\n",
-    "newly_calibrated_models = {\n",
-    "    model.get_hash(): model for model in candidate_space.models\n",
-    "}\n",
-    "calibrated_models.update(newly_calibrated_models)\n",
+    "iteration_results = dummy_calibration_tool(\n",
+    "    problem=select_problem, candidate_space=iteration_results[CANDIDATE_SPACE]\n",
+    ")\n",
+    "local_best_model = petab_select.ui.get_best(\n",
+    "    problem=select_problem, models=iteration_results[MODELS].values()\n",
+    ")\n",
     "\n",
-    "select_problem.exclude_models(newly_calibrated_models.values())"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 17,
-   "id": "c8a36e65",
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Model subspace ID: M1_7\n",
-      "PEtab YAML location: model_selection/petab_problem.yaml\n",
-      "Custom model parameters: {'k1': 'estimate', 'k2': 'estimate', 'k3': 'estimate'}\n",
-      "Model hash: 7c105406ec11716473939a0bbb5281066c1014b54e2480ba126030f5c18a597a27a2ca9247aa60d8262f488165079d1c9e040f9d712ec4e19c2d2122a586f3e5\n",
-      "Model ID: 7c105406ec11716473939a0bbb5281066c1014b54e2480ba126030f5c18a597a27a2ca9247aa60d8262f488165079d1c9e040f9d712ec4e19c2d2122a586f3e5\n",
-      "AIC: 50\n",
-      "\n"
-     ]
-    }
-   ],
-   "source": [
-    "local_best_model = select_problem.get_best(newly_calibrated_models.values())\n",
-    "print_model(local_best_model)"
+    "for candidate_model in iteration_results[MODELS].values():\n",
+    "    if candidate_model.get_hash() == local_best_model.get_hash():\n",
+    "        print(BOLD_TEXT + \"BEST MODEL OF CURRENT ITERATION\" + NORMAL_TEXT)\n",
+    "    print_model(candidate_model)"
    ]
   },
   {
@@ -544,20 +429,19 @@
    "id": "44320e4e",
    "metadata": {},
    "source": [
-    "## Sixth iteration"
+    "## Fifth iteration"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 10,
    "id": "30344b30",
    "metadata": {},
    "outputs": [],
    "source": [
-    "petab_select.ui.candidates(\n",
-    "    problem=select_problem,\n",
-    "    candidate_space=candidate_space,\n",
-    ");"
+    "iteration_results = dummy_calibration_tool(\n",
+    "    problem=select_problem, candidate_space=iteration_results[CANDIDATE_SPACE]\n",
+    ")"
    ]
   },
   {
@@ -570,7 +454,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 11,
    "id": "7843fcb6",
    "metadata": {},
    "outputs": [
@@ -578,12 +462,12 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Number of candidate models: 1.\n"
+      "Number of candidate models: 0.\n"
      ]
     }
    ],
    "source": [
-    "print(f'Number of candidate models: {len(candidate_space.models)}.')"
+    "print(f\"Number of candidate models: {len(iteration_results[MODELS])}.\")"
    ]
   },
   {
@@ -591,12 +475,12 @@
    "id": "c2d1090e",
    "metadata": {},
    "source": [
-    "At this point, the results of the model calibration tool for the different models can be used to select the best model."
+    "At this point, the results of the model calibration tool for the different models can be used to select the best model. You can collect all calibrated models from `iteration_results`. Alternatively, you can access the `CandidateSpace.calibrated_models` attribute."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 12,
    "id": "219d27e4",
    "metadata": {},
    "outputs": [
@@ -607,15 +491,18 @@
       "Model subspace ID: M1_6\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 'estimate', 'k2': 'estimate', 'k3': 0}\n",
-      "Model hash: db8700c079c8347123adc89b7f5112256c4aaebd2af0f6e32e7582f398b2c1e5e85e588cdcc56bab054c001b96a9b42b02174266927f879d7f78e8ac5d2c33e6\n",
-      "Model ID: db8700c079c8347123adc89b7f5112256c4aaebd2af0f6e32e7582f398b2c1e5e85e588cdcc56bab054c001b96a9b42b02174266927f879d7f78e8ac5d2c33e6\n",
-      "AIC: -110\n",
+      "Model hash: M1_6-000\n",
+      "Model ID: M1_6-000\n",
+      "Criterion.AIC: -110\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "best_model = select_problem.get_best(calibrated_models.values())\n",
+    "best_model = petab_select.ui.get_best(\n",
+    "    problem=select_problem,\n",
+    "    models=iteration_results[CANDIDATE_SPACE].calibrated_models.values(),\n",
+    ")\n",
     "print_model(best_model)"
    ]
   },
@@ -624,28 +511,30 @@
    "id": "65d1f895",
    "metadata": {},
    "source": [
-    "## Seventh iteration\n",
+    "## Sixth iteration\n",
     "Note that there can exist additional, uncalibrated models in the model space, after a single forward algorithm terminates. These additional models can be identified with the brute-force method."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 13,
    "id": "cacda13d",
    "metadata": {},
    "outputs": [],
    "source": [
-    "candidate_space = petab_select.BruteForceCandidateSpace()\n",
-    "petab_select.ui.candidates(\n",
+    "candidate_space = petab_select.BruteForceCandidateSpace(\n",
+    "    criterion=select_problem.criterion\n",
+    ")\n",
+    "# candidate_space.calibrated_models = iteration_results[CANDIDATE_SPACE].calibrated_models\n",
+    "petab_select.ui.start_iteration(\n",
     "    problem=select_problem,\n",
     "    candidate_space=candidate_space,\n",
-    "    calibrated_models=calibrated_models,\n",
     ");"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 14,
    "id": "7440cc69",
    "metadata": {},
    "outputs": [
@@ -656,9 +545,9 @@
       "Model subspace ID: M1_4\n",
       "PEtab YAML location: model_selection/petab_problem.yaml\n",
       "Custom model parameters: {'k1': 0.2, 'k2': 'estimate', 'k3': 'estimate'}\n",
-      "Model hash: 38c95dd428b3e31da6969a50db4a1ccbcefe6d8824617d27ec2360e57d55647a25f3fdd45e5f0270786698606cbe496cd94be9495986dade4d1f1d166a4bf911\n",
-      "Model ID: 38c95dd428b3e31da6969a50db4a1ccbcefe6d8824617d27ec2360e57d55647a25f3fdd45e5f0270786698606cbe496cd94be9495986dade4d1f1d166a4bf911\n",
-      "AIC: None\n",
+      "Model hash: M1_4-000\n",
+      "Model ID: M1_4-000\n",
+      "Criterion.AIC: None\n",
       "\n"
      ]
     }
@@ -671,7 +560,7 @@
  ],
  "metadata": {
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -685,7 +574,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.8.10"
+   "version": "3.12.3"
   }
  },
  "nbformat": 4,
diff --git a/doc/index.rst b/doc/index.rst
index 4e086749..697bcba6 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -3,14 +3,63 @@
    You can adapt this file completely to your liking, but it should at least
    contain the root `toctree` directive.
 
-Welcome to petab-select's documentation!
+Welcome to PEtab Select's documentation!
 ========================================
 
+PEtab Select brings
+`model selection <https://en.wikipedia.org/wiki/Model_selection>`_ to
+`PEtab <https://petab.readthedocs.io/>`_. PEtab Select comprises file
+formats, a Python library and a command line interface.
+
+Model selection is the process of choosing the best model from a set of
+candidate models. PEtab Select provides a standardized and compact way to
+specify the candidate model space, implements a number of model selection
+algorithms and criteria.
+
+Supported model selection algorithms:
+
+* brute force
+* `forward selection <https://en.wikipedia.org/wiki/Stepwise_regression#Main_approaches>`_
+* `backward selection <https://en.wikipedia.org/wiki/Stepwise_regression#Main_approaches>`_
+* `FAMoS <https://doi.org/10.1371/journal.pcbi.1007230>`_
+
+Supported model selection criteria:
+
+* (`corrected <https://en.wikipedia.org/wiki/Akaike_information_criterion#Modification_for_small_sample_size>`_)
+  `Akaike Information Criterion <https://en.wikipedia.org/wiki/Akaike_information_criterion#Definition>`_ (AIC / AICc)
+* `Bayesian Information Criterion <https://en.wikipedia.org/wiki/Bayesian_information_criterion#Definition>`_ (BIC)
+
+Model calibration is performed outside of PEtab Select. For example,
+PEtab Select is well-integrated with:
+
+* `BasiCO <https://basico.readthedocs.io/>`_
+  (`example <https://basico.readthedocs.io/en/latest/notebooks/Working_with_PEtab.html#Model-Selection>`__)
+* `PEtab.jl <https://sebapersson.github.io/PEtab.jl>`_
+  (`example <https://sebapersson.github.io/PEtab.jl/stable/pest_select/>`__)
+* `pyPESTO <https://pypesto.readthedocs.io/>`_
+  (`example <https://pypesto.readthedocs.io/en/latest/example/model_selection.html>`__)
+
+Other model calibration tools can easily be integrated using the provided
+Python library or command line interface.
+
+Installation
+------------
+
+The Python 3 package provides both the Python 3 and command-line (CLI)
+interfaces, and can be installed from PyPI, with:
+
+.. code-block:: bash
+
+    pip install petab-select
+
+
 .. toctree::
    :maxdepth: 2
    :caption: Contents:
 
+   problem_definition
    examples
+   Test Suite <test_suite>
    api
 
 
diff --git a/doc/logo/logo-tall.svg b/doc/logo/logo-tall.svg
new file mode 100644
index 00000000..e786c0a1
--- /dev/null
+++ b/doc/logo/logo-tall.svg
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="69.898926mm"
+   height="92.768608mm"
+   viewBox="0 0 69.898926 92.768609"
+   version="1.1"
+   id="svg5"
+   xml:space="preserve"
+   inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
+   sodipodi:docname="008.svg"
+   inkscape:export-filename="001.pdf"
+   inkscape:export-xdpi="299"
+   inkscape:export-ydpi="299"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#eeeeee"
+     borderopacity="1"
+     inkscape:showpageshadow="0"
+     inkscape:pageopacity="0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#505050"
+     inkscape:document-units="mm"
+     showgrid="false"
+     showborder="true"
+     inkscape:zoom="0.44697994"
+     inkscape:cx="-102.9129"
+     inkscape:cy="-275.18014"
+     inkscape:window-width="2490"
+     inkscape:window-height="1376"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer9"><inkscape:grid
+       type="xygrid"
+       id="grid5546"
+       originx="-1285.4584"
+       originy="-1767.9626"
+       spacingy="1"
+       spacingx="1"
+       units="mm"
+       visible="false" /></sodipodi:namedview><defs
+     id="defs2"><filter
+       style="color-interpolation-filters:sRGB"
+       inkscape:label="Drop Shadow"
+       id="filter6"
+       x="-0.0017246602"
+       y="-0.0040821556"
+       width="1.0045991"
+       height="1.0108857"><feFlood
+         result="flood"
+         in="SourceGraphic"
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         id="feFlood5" /><feGaussianBlur
+         result="blur"
+         in="SourceGraphic"
+         stdDeviation="0.050000000000000003"
+         id="feGaussianBlur5" /><feOffset
+         result="offset"
+         in="blur"
+         dx="0.080000000000000002"
+         dy="0.080000000000000002"
+         id="feOffset5"
+         stdDeviation="0.10000000000000001" /><feComposite
+         result="comp1"
+         operator="in"
+         in="flood"
+         in2="offset"
+         id="feComposite5" /><feComposite
+         result="comp2"
+         operator="over"
+         in="SourceGraphic"
+         in2="comp1"
+         id="feComposite6" /></filter><filter
+       style="color-interpolation-filters:sRGB"
+       inkscape:label="Drop Shadow"
+       id="filter6-7"
+       x="-0.0025637498"
+       y="-0.011681241"
+       width="1.0068367"
+       height="1.03115"><feFlood
+         result="flood"
+         in="SourceGraphic"
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         id="feFlood5-5" /><feGaussianBlur
+         result="blur"
+         in="SourceGraphic"
+         stdDeviation="0.050000000000000003"
+         id="feGaussianBlur5-3" /><feOffset
+         result="offset"
+         in="blur"
+         dx="0.080000000000000002"
+         dy="0.080000000000000002"
+         id="feOffset5-5"
+         stdDeviation="0.10000000000000001" /><feComposite
+         result="comp1"
+         operator="in"
+         in="flood"
+         in2="offset"
+         id="feComposite5-6" /><feComposite
+         result="comp2"
+         operator="over"
+         in="SourceGraphic"
+         in2="comp1"
+         id="feComposite6-29" /></filter></defs><g
+     inkscape:groupmode="layer"
+     id="layer8"
+     inkscape:label="Layer 6"
+     transform="translate(-384.51094,-427.87621)" /><g
+     inkscape:groupmode="layer"
+     id="layer10"
+     inkscape:label="Layer 8"
+     transform="translate(-68.631151,48.521198)"><circle
+       style="fill:#aaaa68;fill-opacity:1;stroke-width:0.4"
+       id="path1-7"
+       cx="81.849411"
+       cy="2.6303511"
+       r="4" /><circle
+       style="fill:#80aa68;fill-opacity:1;stroke-width:0.4"
+       id="path1-7-1"
+       cx="106.12141"
+       cy="-9.9494219"
+       r="4" /><circle
+       style="fill:#68dd68;fill-opacity:1;stroke-width:0.4"
+       id="path1-7-2"
+       cx="125.30409"
+       cy="-25.722006"
+       r="4" /><circle
+       style="fill:#686868;fill-opacity:1;stroke-width:0.4"
+       id="path1-7-2-4"
+       cx="122.41429"
+       cy="0.61686224"
+       r="4" /><circle
+       style="fill:#80cc68;fill-opacity:1;stroke-width:0.4"
+       id="path1-7-2-91"
+       cx="88.646866"
+       cy="-17.722006"
+       r="4" /><circle
+       style="fill:#68ff68;fill-opacity:1;stroke-width:0.4"
+       id="path1-7-15"
+       cx="117.30409"
+       cy="-44.521198"
+       r="4" /><path
+       style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-dasharray:none"
+       id="path16"
+       d="m 87.8585,3.5357955 c 1.872514,-0.7306 3.766504,-1.403335 5.636945,-2.139231 1.594607,-0.66820796 3.210199,-1.33382196 4.658285,-2.29296896 0.752655,-0.49852204 0.871135,-0.64678704 1.514864,-1.22904204 0.442796,-0.451962 0.852146,-0.940851 1.172306,-1.488551 0.45966,-0.786353 0.96778,-1.498876 1.27071,-2.341288 0.002,-0.0273 0.022,-0.05972 0.006,-0.08191 -0.38805,-0.537043 -1.1781,1.50999 -1.24438,1.612826 -0.0177,0.02745 -0.0554,0.118131 -0.039,0.08986 1.02037,-1.765668 1.20356,-2.032204 0.77078,-1.382173 -0.4235,0.54333 -1.00932,0.913056 -1.59412,1.259197 -0.573411,0.322135 -1.17137,0.600175 -1.805341,0.778137 -0.05285,0.01212 -0.105449,0.0254 -0.158556,0.03634 -0.318313,0.06562 0.154709,-0.06786 0.08508,0.0993 -0.237535,0.570262 -0.643485,1.05832 -0.897605,1.621388 -0.03449,0.07642 0.14591,-0.0827 0.220475,-0.121028 0.101441,-0.05213 0.20493,-0.100179 0.307396,-0.15027 0.690007,-0.317455 1.372991,-0.648526 2.021575,-1.044552 0.869586,-0.57282 1.422286,-1.454356 1.751526,-2.475632 0.0382,-0.09433 0.0131,-0.203536 0.052,-0.297315 0.008,-0.0181 0.0455,-0.06377 0.0349,-0.0473 -1.0863,1.686716 -0.93547,1.015931 -1.08566,1.714746 0.32096,0.798171 0.56328,1.625481 0.81486,2.447374 0.18453,0.641173 0.37509,1.28057804 0.55983,1.92168004 0,0 1.06373,-1.56170604 1.06373,-1.56170604 v 0 c -0.2103,-0.618807 -0.41458,-1.239697 -0.59321,-1.868556 -0.2516,-0.85869 -0.48883,-1.721512 -0.75211,-2.57669 -0.0317,-0.04826 -0.0584,-0.189259 -0.0952,-0.144775 -1.778043,2.147128 -0.86073,0.98584 -1.21594,1.954821 -0.328811,0.617289 -0.18498,0.343805 0.84931,-1.437923 0.0288,-0.04957 -0.0589,0.0986 -0.0934,0.144375 -0.14358,0.190585 -0.3337,0.332603 -0.52113,0.476936 -0.629141,0.421235 -1.299407,0.773951 -2.000254,1.061593 -0.09894,0.04249 -0.196988,0.08713 -0.296834,0.12746 -0.0739,0.02985 -0.177109,0.01871 -0.224223,0.08299 -0.411615,0.561581 -1.612286,1.677546 -0.941597,1.864559 0.09443,0.02633 0.187553,-0.137904 0.284192,-0.07565 0.654264,-0.198953 1.271325,-0.493241 1.872485,-0.817369 0.591092,-0.325109 1.175261,-0.66983 1.641601,-1.166529 0.56148,-0.689158 0.13056,-0.143592 1.16922,-1.966823 0.0187,-0.0329 0.0688,-0.13448 0.0474,-0.103209 -0.36342,0.532384 -0.6797,1.098251 -1.0745,1.607797 -0.0276,0.03567 -0.0718,-0.05467 -0.10771,-0.082 -0.006,0.01368 -0.0112,0.02738 -0.0169,0.04108 -0.21855,0.571945 -0.71881,1.301546 0.60347,-0.928476 0.09,-0.151841 -0.17165,0.308841 -0.26779,0.456888 -0.36442,0.561163 -0.44808,0.621901 -0.90399,1.1591 -0.630506,0.610211 -0.768142,0.788337 -1.513182,1.311111 -1.426798,1.001146 -3.043412,1.68610004 -4.647977,2.34233504 -1.807002,0.695531 -3.632164,1.34806796 -5.500422,1.86169596 z" /><path
+       style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-dasharray:none"
+       id="path17"
+       d="m 100.82083,-10.518077 c -0.61801,0.146039 -1.262044,0.216339 -1.897054,0.166484 -0.154588,-0.01214 -0.307639,-0.03923 -0.461458,-0.05885 -0.728223,-0.141804 -1.439505,-0.372761 -2.11319,-0.683924 -0.461057,-0.212956 -0.627452,-0.323424 -1.06783,-0.579046 -0.862339,-0.510149 -1.576513,-1.212684 -2.331699,-1.859757 -0.05701,-0.03518 -0.109021,-0.08019 -0.171036,-0.105531 -0.04981,-0.02035 -0.234941,-0.05306 -0.279189,0.0227 -0.349175,0.597741 -0.674101,1.210524 -0.969331,1.836669 -0.02585,0.05482 0.01216,0.120607 0.01802,0.180932 0.0064,0.06571 0.0125,0.131445 0.01875,0.197168 0.04188,0.405241 0.06174,0.812152 0.07124,1.219295 0.03286,0.2273035 -0.05845,0.4922785 0.0472,0.7039025 0.991824,0.745826 1.318926,-2.2104025 1.421086,-2.7921025 0.108953,-0.524269 0.150918,-1.058677 0.229718,-1.587225 0.258109,-0.63278 0.361876,-0.873199 -0.835183,1.469477 -0.0183,0.03582 0.06341,-0.05826 0.103449,-0.06213 0.08803,-0.0085 0.177211,0.0095 0.263966,0.02666 0.243187,0.04812 0.610856,0.176028 0.835616,0.24874 0.914077,0.323252 1.848011,0.572569 2.807368,0.711571 0.09702,0.01098 0.19405,0.02193 0.291076,0.03292 0,0 0.88311,-1.70043 0.88311,-1.70043 v 0 c -0.09794,-0.0042 -0.195879,-0.0084 -0.29382,-0.01259 -0.309782,-0.02622 -0.466992,-0.03324 -0.782534,-0.08856 -0.685474,-0.120174 -1.353405,-0.328729 -2.004246,-0.571513 -0.285351,-0.110075 -0.543067,-0.214321 -0.832219,-0.309081 -0.103141,-0.0338 -0.205438,-0.07305 -0.312246,-0.09235 -0.856611,-0.154779 -1.176367,1.144394 -1.291515,1.914192 -0.04558,0.5139 -0.107276,1.026178 -0.203378,1.533429 -0.02214,0.121341 -0.04358,0.242811 -0.06641,0.364025 -0.03111,0.165139 -0.08308,0.4561325 -0.137675,0.6243475 -0.02037,0.06274 -0.118258,0.234457 -0.07904,0.181419 1.134151,-1.5338715 0.569336,-1.9082535 1.116623,-1.5335935 -1.49e-4,-0.213661 -0.01595,-0.427344 -0.02173,-0.640866 -0.01947,-0.406918 -0.04591,-0.813337 -0.07836,-1.219438 -0.0024,-0.06532 -0.0082,-0.130614 -0.0071,-0.195971 9.53e-4,-0.05756 0.0449,-0.220292 0.01326,-0.172199 -0.333885,0.507479 -0.599363,1.057677 -0.930789,1.566765 -0.02081,0.03197 -0.02416,-0.07628 -0.01538,-0.113406 0.02753,-0.116324 0.182007,-5.29e-4 0.209812,0.01045 0.821561,0.566595 1.536898,1.2802 2.400404,1.790049 0.179765,0.1057985 0.355962,0.2179075 0.539295,0.3173965 0.843119,0.457533 1.74831,0.806894 2.684269,1.016857 0.15885,0.02445 0.316576,0.05789 0.476551,0.07333 0.631574,0.06097 1.270948,-0.003 1.899116,-0.06662 z" /><path
+       style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-dasharray:none"
+       id="path18"
+       d="m 111.17533,-11.218905 c 1.09188,-0.37995 2.1984,-0.732113 3.24078,-1.238592 0.26096,-0.126799 0.51273,-0.271703 0.7691,-0.407556 1.33331,-0.760026 2.44489,-1.839598 3.50013,-2.940227 0.43865,-0.473932 0.82933,-0.99074 1.15523,-1.548614 0.60607,-1.037485 1.17495,-1.971384 1.64217,-3.047281 0.23687,-0.555987 0.41427,-1.133816 0.49388,-1.733047 -0.004,-0.172101 0.0574,-0.368522 0.0123,-0.539118 -0.39619,0.552876 -0.79947,1.100744 -1.18858,1.658626 -0.0237,0.03395 -0.0324,0.07621 -0.0481,0.114488 -0.0566,0.137332 -0.10206,0.279471 -0.16631,0.413385 -0.0378,0.07877 -0.0855,0.152405 -0.12824,0.228607 0.26745,-0.475636 0.53489,-0.951269 0.80234,-1.426905 -0.32901,0.443833 -0.84661,0.55676 -1.36435,0.626271 -0.22378,0.01905 -0.44955,0.03598 -0.67441,0.027 -0.029,-0.0011 -0.0583,-0.01365 -0.0867,-0.0076 -0.0236,0.005 0.0476,0.0072 0.0714,0.01087 -1.38863,2.579169 -1.28439,1.843905 -0.52567,1.482542 0.27736,-0.132098 0.5656,-0.241826 0.85053,-0.356071 0.45405,-0.164171 0.90551,-0.332772 1.32467,-0.574082 0.0495,-0.04542 0.38552,-0.207296 0.40769,-0.263226 0.004,-0.0099 -0.015,-0.01527 -0.0225,-0.02289 0.0226,0.12296 0.0881,0.274781 0.12796,0.392118 0.0547,0.16133 0.18293,0.534466 0.24569,0.677749 0.0618,0.141176 0.13625,0.276516 0.20437,0.414774 0.34889,0.645832 0.75195,1.26038 1.1933,1.846305 0,0 1.01477,-1.613636 1.01477,-1.613636 v 0 c -0.45814,-0.550252 -0.86463,-1.143697 -1.17596,-1.790336 -0.19056,-0.445373 -0.16873,-0.359889 -0.30338,-0.799443 -0.0724,-0.236429 -0.11114,-0.546836 -0.25363,-0.758384 -0.0319,-0.04733 -0.0797,-0.08174 -0.11948,-0.122602 -0.0385,-0.01328 -0.0747,-0.03839 -0.11543,-0.03983 -0.0678,-0.0024 -0.33254,0.289433 -0.41572,0.29559 -0.0825,0.05469 -0.23862,0.160244 -0.32136,0.206171 -0.31332,0.173905 -0.65846,0.287575 -0.98998,0.41974 -0.35013,0.139801 -0.70159,0.278408 -1.05315,0.413909 -0.0552,0.02127 -0.13713,0.0055 -0.16839,0.05573 -0.36259,0.582088 -0.65985,1.202404 -0.98978,1.803603 0.0353,0.02408 0.0659,0.0574 0.10602,0.07225 0.0328,0.01214 0.22066,0.0031 0.24072,0.0024 0.19231,-0.0075 0.38443,-0.02064 0.57565,-0.04267 0.54184,-0.07147 1.11624,-0.153011 1.51266,-0.566018 0.36876,-0.621321 0.74361,-1.239065 1.10627,-1.863966 0.12226,-0.210658 0.21447,-0.457954 0.29541,-0.68743 0.0171,-0.04856 0.076,-0.188319 0.0439,-0.148097 -0.40365,0.505134 -0.77665,1.034023 -1.16497,1.551037 -0.005,0.151181 -0.005,0.31664 -0.0277,0.471266 -0.0822,0.563282 -0.2554,1.106368 -0.48017,1.628986 -0.0652,0.146204 -0.12642,0.294198 -0.19545,0.438613 -0.26162,0.547317 -0.70581,1.295678 0.36613,-0.549029 0.10425,-0.179403 -0.20753,0.359614 -0.3211,0.533271 -0.33161,0.507082 -0.41825,0.583083 -0.80981,1.053526 -0.50719,0.545443 -1.02804,1.069951 -1.58157,1.56909 -0.33802,0.304816 -0.82986,0.729266 -1.19983,0.992873 -0.22097,0.15745 -0.45598,0.294214 -0.68397,0.441322 -0.25241,0.138001 -0.49924,0.286753 -0.75723,0.414009 -0.87985,0.433983 -1.83485,0.767816 -2.78895,0.996511 -0.10957,0.02627 -0.2215,0.04142 -0.33225,0.06213 z" /><path
+       style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-dasharray:none"
+       id="path19"
+       d="m 121.10971,-29.205235 c -0.54745,-0.308393 -0.95925,-0.802927 -1.3473,-1.285672 -0.55327,-0.761525 -0.95993,-1.611248 -1.30323,-2.483779 -0.2709,-0.745097 -0.50297,-1.505285 -0.64849,-2.285035 -0.10125,-0.662688 -0.13008,-1.335421 -0.10691,-2.004816 0.002,-0.465064 0.11512,-0.916359 0.20451,-1.369525 0.0496,-0.1551 0.062,-0.471132 0.14694,-0.601455 -1.69382,2.183642 -1.51655,2.110536 -0.3539,0.04337 0.0346,-0.06153 -0.075,0.119665 -0.11403,0.178473 -0.14331,0.215773 -0.29752,0.423121 -0.45392,0.62944 -0.32271,0.416619 -0.69585,0.791707 -1.0829,1.148168 -0.3025,0.264801 -0.58111,0.556281 -0.88401,0.820378 -0.55524,0.394959 -1.74805,1.784234 -0.8406,1.878912 0.13641,0.01423 0.46302,-0.177045 0.57802,-0.235179 0.4373,-0.218695 0.84998,-0.491464 1.23007,-0.798736 0.79858,-0.743617 1.28514,-1.771326 1.67781,-2.781169 0.14042,-0.312583 0.19836,-0.651183 0.29712,-0.976242 0.33123,-0.614714 0.14774,-0.272951 -0.89033,1.565222 -0.0224,0.0396 0.0302,-0.09097 0.10257,-0.100073 0.089,-0.01118 0.17578,0.05967 0.2427,0.09779 0.35781,0.260008 0.66012,0.586235 0.96869,0.900857 0.28064,0.292375 0.61182,0.517699 0.97182,0.699004 0.26779,0.124727 0.55274,0.198819 0.84421,0.239438 0.0266,0.0043 0.0532,0.0085 0.0797,0.01278 0,0 0.92195,-1.747372 0.92195,-1.747372 v 0 c -0.0295,-0.0038 -0.059,-0.0076 -0.0886,-0.01138 -0.27332,-0.01971 -0.54269,-0.07181 -0.79391,-0.185737 -0.34241,-0.169335 -0.6554,-0.381722 -0.92106,-0.659344 -0.3219,-0.323883 -0.64489,-0.649795 -1.00525,-0.931356 -0.1266,-0.08191 -0.20451,-0.149097 -0.35319,-0.183759 -0.0265,-0.0062 -0.20881,-0.02246 -0.24151,0.0301 -0.9877,1.587948 -0.87006,1.1069 -1.03658,1.83793 -0.0698,0.319827 -0.17085,0.631443 -0.29541,0.934253 -0.26853,0.651553 -0.82068,1.516669 0.67547,-1.042783 0.0375,-0.06419 -0.0746,0.128783 -0.11666,0.190134 -0.041,0.05986 -0.0847,0.118029 -0.13204,0.17303 -0.0476,0.05535 -0.1015,0.104973 -0.15225,0.15746 -0.19614,0.173468 -0.33815,0.27986 -0.55519,0.425846 -0.20679,0.139094 -0.39882,0.303357 -0.63529,0.392429 -0.15584,0.08106 -0.21482,0.115981 -0.37014,0.182131 -0.0384,0.01635 -0.10794,0.0036 -0.11706,0.04437 -0.009,0.0419 0.10497,0.05803 0.0855,0.09632 -0.29178,0.574764 -0.66478,1.104767 -0.98054,1.666713 -0.0173,0.03073 0.0545,-0.04473 0.0817,-0.0671 0.32808,-0.25525 0.59167,-0.584077 0.90823,-0.853383 0.39478,-0.352459 0.77749,-0.720213 1.11309,-1.130668 0.15645,-0.201417 0.30885,-0.404587 0.45831,-0.611215 0.0386,-0.05337 0.0832,-0.102789 0.11661,-0.15953 1.4843,-2.517623 1.37664,-2.22891 -0.15809,-0.298731 -0.006,0.198931 -0.0932,0.390927 -0.10593,0.589849 -0.0801,0.447558 -0.16026,0.896787 -0.15229,1.35366 -7.9e-4,0.675286 0.0212,1.355512 0.14607,2.020708 0.16633,0.776699 0.41162,1.534132 0.69763,2.274864 0.36387,0.882973 0.77499,1.748794 1.3438,2.521325 0.40541,0.509971 0.82874,1.020473 1.35506,1.411235 z" /></g><g
+     inkscape:groupmode="layer"
+     id="layer9"
+     inkscape:label="Layer 7"
+     transform="translate(-68.631151,48.521198)"><text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.6333px;line-height:1;font-family:Cabin;-inkscape-font-specification:'Cabin, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583;filter:url(#filter6)"
+       x="66.94352"
+       y="29.392199"
+       id="text12"><tspan
+         sodipodi:role="line"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.6333px;line-height:1;font-family:Cabin;-inkscape-font-specification:'Cabin, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#0055d4;fill-opacity:1;stroke-width:0.264583"
+         x="66.94352"
+         y="29.392199"
+         id="tspan13">petab</tspan></text><text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.1111px;line-height:1;font-family:Cabin;-inkscape-font-specification:'Cabin, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583;filter:url(#filter6-7)"
+       x="82.067032"
+       y="43.891041"
+       id="text12-1"><tspan
+         sodipodi:role="line"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.1111px;line-height:1;font-family:Cabin;-inkscape-font-specification:'Cabin, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583"
+         x="82.067032"
+         y="43.891041"
+         id="tspan13-7">SELECT</tspan></text></g><g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="bgs"
+     style="display:inline"
+     transform="translate(-384.51094,-427.8762)" /></svg>
diff --git a/doc/logo/logo-wide.svg b/doc/logo/logo-wide.svg
new file mode 100644
index 00000000..0c51f8a2
--- /dev/null
+++ b/doc/logo/logo-wide.svg
@@ -0,0 +1,194 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="153.34982mm"
+   height="55.15155mm"
+   viewBox="0 0 153.34982 55.151551"
+   version="1.1"
+   id="svg5"
+   xml:space="preserve"
+   inkscape:version="1.4 (1:1.4+202410161351+e7c3feb100)"
+   sodipodi:docname="logo-wide.svg"
+   inkscape:export-filename="001.pdf"
+   inkscape:export-xdpi="299"
+   inkscape:export-ydpi="299"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+     id="namedview7"
+     pagecolor="#ffffff"
+     bordercolor="#eeeeee"
+     borderopacity="1"
+     inkscape:showpageshadow="0"
+     inkscape:pageopacity="0"
+     inkscape:pagecheckerboard="true"
+     inkscape:deskcolor="#505050"
+     inkscape:document-units="mm"
+     showgrid="false"
+     showborder="true"
+     inkscape:zoom="1.1070433"
+     inkscape:cx="380.7439"
+     inkscape:cy="70.9096"
+     inkscape:window-width="2494"
+     inkscape:window-height="1371"
+     inkscape:window-x="66"
+     inkscape:window-y="1472"
+     inkscape:window-maximized="1"
+     inkscape:current-layer="layer9"><inkscape:grid
+       type="xygrid"
+       id="grid5546"
+       originx="-1294.6766"
+       originy="-1767.9626"
+       spacingy="1"
+       spacingx="1"
+       units="mm"
+       visible="false" /></sodipodi:namedview><defs
+     id="defs2"><filter
+       style="color-interpolation-filters:sRGB"
+       inkscape:label="Drop Shadow"
+       id="filter6"
+       x="-0.0017246602"
+       y="-0.0040821556"
+       width="1.0045991"
+       height="1.0108857"><feFlood
+         result="flood"
+         in="SourceGraphic"
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         id="feFlood5" /><feGaussianBlur
+         result="blur"
+         in="SourceGraphic"
+         stdDeviation="0.050000000000000003"
+         id="feGaussianBlur5" /><feOffset
+         result="offset"
+         in="blur"
+         dx="0.080000000000000002"
+         dy="0.080000000000000002"
+         id="feOffset5"
+         stdDeviation="0.10000000000000001" /><feComposite
+         result="comp1"
+         operator="in"
+         in="flood"
+         in2="offset"
+         id="feComposite5" /><feComposite
+         result="comp2"
+         operator="over"
+         in="SourceGraphic"
+         in2="comp1"
+         id="feComposite6" /></filter><filter
+       style="color-interpolation-filters:sRGB"
+       inkscape:label="Drop Shadow"
+       id="filter6-7"
+       x="-0.0025637455"
+       y="-0.011681241"
+       width="1.0068367"
+       height="1.03115"><feFlood
+         result="flood"
+         in="SourceGraphic"
+         flood-opacity="0.498039"
+         flood-color="rgb(0,0,0)"
+         id="feFlood5-5" /><feGaussianBlur
+         result="blur"
+         in="SourceGraphic"
+         stdDeviation="0.050000000000000003"
+         id="feGaussianBlur5-3" /><feOffset
+         result="offset"
+         in="blur"
+         dx="0.080000000000000002"
+         dy="0.080000000000000002"
+         id="feOffset5-5"
+         stdDeviation="0.10000000000000001" /><feComposite
+         result="comp1"
+         operator="in"
+         in="flood"
+         in2="offset"
+         id="feComposite5-6" /><feComposite
+         result="comp2"
+         operator="over"
+         in="SourceGraphic"
+         in2="comp1"
+         id="feComposite6-29" /></filter></defs><g
+     inkscape:groupmode="layer"
+     id="layer8"
+     inkscape:label="Layer 6"
+     transform="translate(-393.7292,-427.87621)" /><g
+     inkscape:groupmode="layer"
+     id="layer10"
+     inkscape:label="Layer 8"
+     transform="translate(-77.849411,48.521198)"><g
+       id="g2"><circle
+         style="fill:#aaaa68;fill-opacity:1;stroke-width:0.4"
+         id="path1-7"
+         cx="81.849411"
+         cy="2.6303511"
+         r="4" /><circle
+         style="fill:#80aa68;fill-opacity:1;stroke-width:0.4"
+         id="path1-7-1"
+         cx="106.12141"
+         cy="-9.9494219"
+         r="4" /><circle
+         style="fill:#68dd68;fill-opacity:1;stroke-width:0.4"
+         id="path1-7-2"
+         cx="125.30409"
+         cy="-25.722006"
+         r="4" /><circle
+         style="fill:#686868;fill-opacity:1;stroke-width:0.4"
+         id="path1-7-2-4"
+         cx="122.41429"
+         cy="0.61686224"
+         r="4" /><circle
+         style="fill:#80cc68;fill-opacity:1;stroke-width:0.4"
+         id="path1-7-2-91"
+         cx="88.646866"
+         cy="-17.722006"
+         r="4" /><circle
+         style="fill:#68ff68;fill-opacity:1;stroke-width:0.4"
+         id="path1-7-15"
+         cx="117.30409"
+         cy="-44.521198"
+         r="4" /><path
+         style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-dasharray:none"
+         id="path16"
+         d="m 87.8585,3.5357955 c 1.872514,-0.7306 3.766504,-1.403335 5.636945,-2.139231 1.594607,-0.66820796 3.210199,-1.33382196 4.658285,-2.29296896 0.752655,-0.49852204 0.871135,-0.64678704 1.514864,-1.22904204 0.442796,-0.451962 0.852146,-0.940851 1.172306,-1.488551 0.45966,-0.786353 0.96778,-1.498876 1.27071,-2.341288 0.002,-0.0273 0.022,-0.05972 0.006,-0.08191 -0.38805,-0.537043 -1.1781,1.50999 -1.24438,1.612826 -0.0177,0.02745 -0.0554,0.118131 -0.039,0.08986 1.02037,-1.765668 1.20356,-2.032204 0.77078,-1.382173 -0.4235,0.54333 -1.00932,0.913056 -1.59412,1.259197 -0.573411,0.322135 -1.17137,0.600175 -1.805341,0.778137 -0.05285,0.01212 -0.105449,0.0254 -0.158556,0.03634 -0.318313,0.06562 0.154709,-0.06786 0.08508,0.0993 -0.237535,0.570262 -0.643485,1.05832 -0.897605,1.621388 -0.03449,0.07642 0.14591,-0.0827 0.220475,-0.121028 0.101441,-0.05213 0.20493,-0.100179 0.307396,-0.15027 0.690007,-0.317455 1.372991,-0.648526 2.021575,-1.044552 0.869586,-0.57282 1.422286,-1.454356 1.751526,-2.475632 0.0382,-0.09433 0.0131,-0.203536 0.052,-0.297315 0.008,-0.0181 0.0455,-0.06377 0.0349,-0.0473 -1.0863,1.686716 -0.93547,1.015931 -1.08566,1.714746 0.32096,0.798171 0.56328,1.625481 0.81486,2.447374 0.18453,0.641173 0.37509,1.28057804 0.55983,1.92168004 0,0 1.06373,-1.56170604 1.06373,-1.56170604 v 0 c -0.2103,-0.618807 -0.41458,-1.239697 -0.59321,-1.868556 -0.2516,-0.85869 -0.48883,-1.721512 -0.75211,-2.57669 -0.0317,-0.04826 -0.0584,-0.189259 -0.0952,-0.144775 -1.778043,2.147128 -0.86073,0.98584 -1.21594,1.954821 -0.328811,0.617289 -0.18498,0.343805 0.84931,-1.437923 0.0288,-0.04957 -0.0589,0.0986 -0.0934,0.144375 -0.14358,0.190585 -0.3337,0.332603 -0.52113,0.476936 -0.629141,0.421235 -1.299407,0.773951 -2.000254,1.061593 -0.09894,0.04249 -0.196988,0.08713 -0.296834,0.12746 -0.0739,0.02985 -0.177109,0.01871 -0.224223,0.08299 -0.411615,0.561581 -1.612286,1.677546 -0.941597,1.864559 0.09443,0.02633 0.187553,-0.137904 0.284192,-0.07565 0.654264,-0.198953 1.271325,-0.493241 1.872485,-0.817369 0.591092,-0.325109 1.175261,-0.66983 1.641601,-1.166529 0.56148,-0.689158 0.13056,-0.143592 1.16922,-1.966823 0.0187,-0.0329 0.0688,-0.13448 0.0474,-0.103209 -0.36342,0.532384 -0.6797,1.098251 -1.0745,1.607797 -0.0276,0.03567 -0.0718,-0.05467 -0.10771,-0.082 -0.006,0.01368 -0.0112,0.02738 -0.0169,0.04108 -0.21855,0.571945 -0.71881,1.301546 0.60347,-0.928476 0.09,-0.151841 -0.17165,0.308841 -0.26779,0.456888 -0.36442,0.561163 -0.44808,0.621901 -0.90399,1.1591 -0.630506,0.610211 -0.768142,0.788337 -1.513182,1.311111 -1.426798,1.001146 -3.043412,1.68610004 -4.647977,2.34233504 -1.807002,0.695531 -3.632164,1.34806796 -5.500422,1.86169596 z" /><path
+         style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-dasharray:none"
+         id="path17"
+         d="m 100.82083,-10.518077 c -0.61801,0.146039 -1.262044,0.216339 -1.897054,0.166484 -0.154588,-0.01214 -0.307639,-0.03923 -0.461458,-0.05885 -0.728223,-0.141804 -1.439505,-0.372761 -2.11319,-0.683924 -0.461057,-0.212956 -0.627452,-0.323424 -1.06783,-0.579046 -0.862339,-0.510149 -1.576513,-1.212684 -2.331699,-1.859757 -0.05701,-0.03518 -0.109021,-0.08019 -0.171036,-0.105531 -0.04981,-0.02035 -0.234941,-0.05306 -0.279189,0.0227 -0.349175,0.597741 -0.674101,1.210524 -0.969331,1.836669 -0.02585,0.05482 0.01216,0.120607 0.01802,0.180932 0.0064,0.06571 0.0125,0.131445 0.01875,0.197168 0.04188,0.405241 0.06174,0.812152 0.07124,1.219295 0.03286,0.2273035 -0.05845,0.4922785 0.0472,0.7039025 0.991824,0.745826 1.318926,-2.2104025 1.421086,-2.7921025 0.108953,-0.524269 0.150918,-1.058677 0.229718,-1.587225 0.258109,-0.63278 0.361876,-0.873199 -0.835183,1.469477 -0.0183,0.03582 0.06341,-0.05826 0.103449,-0.06213 0.08803,-0.0085 0.177211,0.0095 0.263966,0.02666 0.243187,0.04812 0.610856,0.176028 0.835616,0.24874 0.914077,0.323252 1.848011,0.572569 2.807368,0.711571 0.09702,0.01098 0.19405,0.02193 0.291076,0.03292 0,0 0.88311,-1.70043 0.88311,-1.70043 v 0 c -0.09794,-0.0042 -0.195879,-0.0084 -0.29382,-0.01259 -0.309782,-0.02622 -0.466992,-0.03324 -0.782534,-0.08856 -0.685474,-0.120174 -1.353405,-0.328729 -2.004246,-0.571513 -0.285351,-0.110075 -0.543067,-0.214321 -0.832219,-0.309081 -0.103141,-0.0338 -0.205438,-0.07305 -0.312246,-0.09235 -0.856611,-0.154779 -1.176367,1.144394 -1.291515,1.914192 -0.04558,0.5139 -0.107276,1.026178 -0.203378,1.533429 -0.02214,0.121341 -0.04358,0.242811 -0.06641,0.364025 -0.03111,0.165139 -0.08308,0.4561325 -0.137675,0.6243475 -0.02037,0.06274 -0.118258,0.234457 -0.07904,0.181419 1.134151,-1.5338715 0.569336,-1.9082535 1.116623,-1.5335935 -1.49e-4,-0.213661 -0.01595,-0.427344 -0.02173,-0.640866 -0.01947,-0.406918 -0.04591,-0.813337 -0.07836,-1.219438 -0.0024,-0.06532 -0.0082,-0.130614 -0.0071,-0.195971 9.53e-4,-0.05756 0.0449,-0.220292 0.01326,-0.172199 -0.333885,0.507479 -0.599363,1.057677 -0.930789,1.566765 -0.02081,0.03197 -0.02416,-0.07628 -0.01538,-0.113406 0.02753,-0.116324 0.182007,-5.29e-4 0.209812,0.01045 0.821561,0.566595 1.536898,1.2802 2.400404,1.790049 0.179765,0.1057985 0.355962,0.2179075 0.539295,0.3173965 0.843119,0.457533 1.74831,0.806894 2.684269,1.016857 0.15885,0.02445 0.316576,0.05789 0.476551,0.07333 0.631574,0.06097 1.270948,-0.003 1.899116,-0.06662 z" /><path
+         style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-dasharray:none"
+         id="path18"
+         d="m 111.17533,-11.218905 c 1.09188,-0.37995 2.1984,-0.732113 3.24078,-1.238592 0.26096,-0.126799 0.51273,-0.271703 0.7691,-0.407556 1.33331,-0.760026 2.44489,-1.839598 3.50013,-2.940227 0.43865,-0.473932 0.82933,-0.99074 1.15523,-1.548614 0.60607,-1.037485 1.17495,-1.971384 1.64217,-3.047281 0.23687,-0.555987 0.41427,-1.133816 0.49388,-1.733047 -0.004,-0.172101 0.0574,-0.368522 0.0123,-0.539118 -0.39619,0.552876 -0.79947,1.100744 -1.18858,1.658626 -0.0237,0.03395 -0.0324,0.07621 -0.0481,0.114488 -0.0566,0.137332 -0.10206,0.279471 -0.16631,0.413385 -0.0378,0.07877 -0.0855,0.152405 -0.12824,0.228607 0.26745,-0.475636 0.53489,-0.951269 0.80234,-1.426905 -0.32901,0.443833 -0.84661,0.55676 -1.36435,0.626271 -0.22378,0.01905 -0.44955,0.03598 -0.67441,0.027 -0.029,-0.0011 -0.0583,-0.01365 -0.0867,-0.0076 -0.0236,0.005 0.0476,0.0072 0.0714,0.01087 -1.38863,2.579169 -1.28439,1.843905 -0.52567,1.482542 0.27736,-0.132098 0.5656,-0.241826 0.85053,-0.356071 0.45405,-0.164171 0.90551,-0.332772 1.32467,-0.574082 0.0495,-0.04542 0.38552,-0.207296 0.40769,-0.263226 0.004,-0.0099 -0.015,-0.01527 -0.0225,-0.02289 0.0226,0.12296 0.0881,0.274781 0.12796,0.392118 0.0547,0.16133 0.18293,0.534466 0.24569,0.677749 0.0618,0.141176 0.13625,0.276516 0.20437,0.414774 0.34889,0.645832 0.75195,1.26038 1.1933,1.846305 0,0 1.01477,-1.613636 1.01477,-1.613636 v 0 c -0.45814,-0.550252 -0.86463,-1.143697 -1.17596,-1.790336 -0.19056,-0.445373 -0.16873,-0.359889 -0.30338,-0.799443 -0.0724,-0.236429 -0.11114,-0.546836 -0.25363,-0.758384 -0.0319,-0.04733 -0.0797,-0.08174 -0.11948,-0.122602 -0.0385,-0.01328 -0.0747,-0.03839 -0.11543,-0.03983 -0.0678,-0.0024 -0.33254,0.289433 -0.41572,0.29559 -0.0825,0.05469 -0.23862,0.160244 -0.32136,0.206171 -0.31332,0.173905 -0.65846,0.287575 -0.98998,0.41974 -0.35013,0.139801 -0.70159,0.278408 -1.05315,0.413909 -0.0552,0.02127 -0.13713,0.0055 -0.16839,0.05573 -0.36259,0.582088 -0.65985,1.202404 -0.98978,1.803603 0.0353,0.02408 0.0659,0.0574 0.10602,0.07225 0.0328,0.01214 0.22066,0.0031 0.24072,0.0024 0.19231,-0.0075 0.38443,-0.02064 0.57565,-0.04267 0.54184,-0.07147 1.11624,-0.153011 1.51266,-0.566018 0.36876,-0.621321 0.74361,-1.239065 1.10627,-1.863966 0.12226,-0.210658 0.21447,-0.457954 0.29541,-0.68743 0.0171,-0.04856 0.076,-0.188319 0.0439,-0.148097 -0.40365,0.505134 -0.77665,1.034023 -1.16497,1.551037 -0.005,0.151181 -0.005,0.31664 -0.0277,0.471266 -0.0822,0.563282 -0.2554,1.106368 -0.48017,1.628986 -0.0652,0.146204 -0.12642,0.294198 -0.19545,0.438613 -0.26162,0.547317 -0.70581,1.295678 0.36613,-0.549029 0.10425,-0.179403 -0.20753,0.359614 -0.3211,0.533271 -0.33161,0.507082 -0.41825,0.583083 -0.80981,1.053526 -0.50719,0.545443 -1.02804,1.069951 -1.58157,1.56909 -0.33802,0.304816 -0.82986,0.729266 -1.19983,0.992873 -0.22097,0.15745 -0.45598,0.294214 -0.68397,0.441322 -0.25241,0.138001 -0.49924,0.286753 -0.75723,0.414009 -0.87985,0.433983 -1.83485,0.767816 -2.78895,0.996511 -0.10957,0.02627 -0.2215,0.04142 -0.33225,0.06213 z" /><path
+         style="fill:#000000;fill-opacity:1;stroke-width:0.2;stroke-dasharray:none"
+         id="path19"
+         d="m 121.10971,-29.205235 c -0.54745,-0.308393 -0.95925,-0.802927 -1.3473,-1.285672 -0.55327,-0.761525 -0.95993,-1.611248 -1.30323,-2.483779 -0.2709,-0.745097 -0.50297,-1.505285 -0.64849,-2.285035 -0.10125,-0.662688 -0.13008,-1.335421 -0.10691,-2.004816 0.002,-0.465064 0.11512,-0.916359 0.20451,-1.369525 0.0496,-0.1551 0.062,-0.471132 0.14694,-0.601455 -1.69382,2.183642 -1.51655,2.110536 -0.3539,0.04337 0.0346,-0.06153 -0.075,0.119665 -0.11403,0.178473 -0.14331,0.215773 -0.29752,0.423121 -0.45392,0.62944 -0.32271,0.416619 -0.69585,0.791707 -1.0829,1.148168 -0.3025,0.264801 -0.58111,0.556281 -0.88401,0.820378 -0.55524,0.394959 -1.74805,1.784234 -0.8406,1.878912 0.13641,0.01423 0.46302,-0.177045 0.57802,-0.235179 0.4373,-0.218695 0.84998,-0.491464 1.23007,-0.798736 0.79858,-0.743617 1.28514,-1.771326 1.67781,-2.781169 0.14042,-0.312583 0.19836,-0.651183 0.29712,-0.976242 0.33123,-0.614714 0.14774,-0.272951 -0.89033,1.565222 -0.0224,0.0396 0.0302,-0.09097 0.10257,-0.100073 0.089,-0.01118 0.17578,0.05967 0.2427,0.09779 0.35781,0.260008 0.66012,0.586235 0.96869,0.900857 0.28064,0.292375 0.61182,0.517699 0.97182,0.699004 0.26779,0.124727 0.55274,0.198819 0.84421,0.239438 0.0266,0.0043 0.0532,0.0085 0.0797,0.01278 0,0 0.92195,-1.747372 0.92195,-1.747372 v 0 c -0.0295,-0.0038 -0.059,-0.0076 -0.0886,-0.01138 -0.27332,-0.01971 -0.54269,-0.07181 -0.79391,-0.185737 -0.34241,-0.169335 -0.6554,-0.381722 -0.92106,-0.659344 -0.3219,-0.323883 -0.64489,-0.649795 -1.00525,-0.931356 -0.1266,-0.08191 -0.20451,-0.149097 -0.35319,-0.183759 -0.0265,-0.0062 -0.20881,-0.02246 -0.24151,0.0301 -0.9877,1.587948 -0.87006,1.1069 -1.03658,1.83793 -0.0698,0.319827 -0.17085,0.631443 -0.29541,0.934253 -0.26853,0.651553 -0.82068,1.516669 0.67547,-1.042783 0.0375,-0.06419 -0.0746,0.128783 -0.11666,0.190134 -0.041,0.05986 -0.0847,0.118029 -0.13204,0.17303 -0.0476,0.05535 -0.1015,0.104973 -0.15225,0.15746 -0.19614,0.173468 -0.33815,0.27986 -0.55519,0.425846 -0.20679,0.139094 -0.39882,0.303357 -0.63529,0.392429 -0.15584,0.08106 -0.21482,0.115981 -0.37014,0.182131 -0.0384,0.01635 -0.10794,0.0036 -0.11706,0.04437 -0.009,0.0419 0.10497,0.05803 0.0855,0.09632 -0.29178,0.574764 -0.66478,1.104767 -0.98054,1.666713 -0.0173,0.03073 0.0545,-0.04473 0.0817,-0.0671 0.32808,-0.25525 0.59167,-0.584077 0.90823,-0.853383 0.39478,-0.352459 0.77749,-0.720213 1.11309,-1.130668 0.15645,-0.201417 0.30885,-0.404587 0.45831,-0.611215 0.0386,-0.05337 0.0832,-0.102789 0.11661,-0.15953 1.4843,-2.517623 1.37664,-2.22891 -0.15809,-0.298731 -0.006,0.198931 -0.0932,0.390927 -0.10593,0.589849 -0.0801,0.447558 -0.16026,0.896787 -0.15229,1.35366 -7.9e-4,0.675286 0.0212,1.355512 0.14607,2.020708 0.16633,0.776699 0.41162,1.534132 0.69763,2.274864 0.36387,0.882973 0.77499,1.748794 1.3438,2.521325 0.40541,0.509971 0.82874,1.020473 1.35506,1.411235 z" /></g></g><g
+     inkscape:groupmode="layer"
+     id="layer9"
+     inkscape:label="Layer 7"
+     transform="translate(-77.849411,48.521198)"><g
+       id="g1"
+       transform="matrix(1.35,0,0,1.35,44.183639,-55.597102)"><text
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.6333px;line-height:1;font-family:Cabin;-inkscape-font-specification:'Cabin, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583;filter:url(#filter6)"
+         x="66.94352"
+         y="29.392199"
+         id="text12"><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:29.6333px;line-height:1;font-family:Cabin;-inkscape-font-specification:'Cabin, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#0055d4;fill-opacity:1;stroke-width:0.264583"
+           x="66.94352"
+           y="29.392199"
+           id="tspan13">petab</tspan></text><text
+         xml:space="preserve"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:14.1111px;line-height:1;font-family:Cabin;-inkscape-font-specification:'Cabin, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583;filter:url(#filter6-7)"
+         x="82.067032"
+         y="43.891041"
+         id="text12-1"><tspan
+           sodipodi:role="line"
+           style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:14.1111px;line-height:1;font-family:Cabin;-inkscape-font-specification:'Cabin, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.264583"
+           x="82.067032"
+           y="43.891041"
+           id="tspan13-7">SELECT</tspan></text></g></g><g
+     inkscape:groupmode="layer"
+     id="layer6"
+     inkscape:label="bgs"
+     style="display:inline"
+     transform="translate(-393.7292,-427.8762)" /></svg>
diff --git a/doc/problem_definition.rst b/doc/problem_definition.rst
new file mode 100644
index 00000000..eb05699d
--- /dev/null
+++ b/doc/problem_definition.rst
@@ -0,0 +1,143 @@
+Problem definition and file formats
+===================================
+
+Model selection problems for PEtab Select are defined by the following files:
+
+#. a general description of the model selection problem,
+#. a specification of the model space, and
+#. (optionally) a specification of the initial candidate model.
+
+The different file formats are described below.
+
+1. Selection problem
+--------------------
+
+A YAML file with a description of the model selection problem.
+
+.. code-block:: yaml
+
+   format_version: # string.
+   criterion: # string.
+   method: # string.
+   model_space_files: # list[string]. Filenames.
+   candidate_space_arguments: # list[string] (optional). Filenames.
+
+- ``format_version``: The version of the model selection extension format
+  (e.g. ``1``)
+- ``criterion``: The criterion by which models should be compared
+  (e.g. ``AIC``)
+- ``method``: The method by which model candidates should be generated
+  (e.g. ``forward``)
+- ``model_space_files``: The filenames of model space files.
+- ``candidate_space_arguments``: Additional arguments used to generate
+  candidate models during model selection. For example, an initial candidate
+  model can be specified with the following code, where
+  ``predecessor_model.yaml`` is a valid model file. Additional arguments are
+  provided in the documentation of the ``CandidateSpace`` class.
+
+.. code-block:: yaml
+
+   candidate_space_arguments:
+     predecessor_model: predecessor_model.yaml
+
+2. Model space
+--------------
+
+A TSV file with candidate models, in compressed or uncompressed format.
+Each row defines a model subspace, by specifying value(s) that each parameter
+can take. The models in a model subspace are all combinations of values across
+all parameters.
+
+.. list-table::
+   :header-rows: 1
+
+   * - ``model_subspace_id``
+     - ``petab_yaml``
+     - ``parameter_id_1``
+     - ...
+     - ``parameter_id_n``
+   * - (unique) [string]
+     - [string]
+     - (``;``-delimited list) [string/float]
+     - ...
+     - ...
+
+- ``model_subspace_id``: An ID for the model subspace.
+- ``petab_yaml``: The PEtab YAML filename that serves as the basis of all
+  models in this subspace.
+- ``parameter_id_1`` ... ``parameter_id_n``: Specify the values that a
+  parameter can take in the model subspace. For example, this could be:
+
+  - a single value
+
+    - ``0.0``
+    - ``1.0``
+    - ``estimate``
+
+  - one of several possible values, as a ``;``-delimited list
+
+    - ``0.0;1.1;estimate`` (the parameter can take the values ``0.0`` or
+      ``1.1``, or be estimated)
+
+Using the ``;``-delimited list format, a model subspace that has two parameters
+(``p1, p2``) and six models:
+
+- ``p1:=0, p2:=10``
+- ``p1:=0, p2:=20``
+- ``p1:=0, p2:=estimate``
+- ``p1:=estimate, p2:=10``
+- ``p1:=estimate, p2:=20``
+- ``p1:=estimate, p2:=estimate``
+
+can be specified like
+
+.. list-table::
+   :header-rows: 1
+
+   * - model_subspace_id
+     - petab_yaml
+     - p1
+     - p2
+   * - subspace1
+     - petab_problem.yaml
+     - 0;estimate
+     - 10;20;estimate
+
+3. Model(s) (Predecessor models / model interchange / report)
+-------------------------------------------------------------
+
+- *Predecessor models* are used to initialize a compatible model selection
+  method.
+- *Model interchange* refers to the format used to transfer model information
+  between PEtab Select and a PEtab-compatible calibration tool, during the
+  model selection process.
+- *Report* refers to the final results of the model selection process, which
+  may include calibration results from any calibrated models, or just the
+  selected model.
+
+Here, the format for a single model is shown. Multiple models can be specified
+as a YAML list of the same format. The only required key is the ``petab_yaml``,
+as a model requires a PEtab problem. Other keys are required in different
+contexts (for example, model comparison will require ``criteria``).
+
+.. code-block:: yaml
+
+   criteria: # dict[string, float] (optional). Criterion ID => criterion value.
+   estimated_parameters: # dict[string, float] (optional). Parameter ID => parameter value.
+   model_hash: # string (optional).
+   model_id: # string (optional).
+   model_subspace_id: # string (optional).
+   model_subspace_indices: # string (optional).
+   parameters: # dict[string, float] (optional). Parameter ID => parameter value or "estimate".
+   petab_yaml: # string.
+   predecessor_model_hash: # string (optional).
+
+- ``criteria``: The value of the criterion by which model selection was performed, at least. Optionally, other criterion values too.
+- ``estimated_parameters``: Parameter estimates, not only of parameters specified to be estimated in a model space file, but also parameters specified to be estimated in the original PEtab problem of the model.
+- ``model_hash``: The model hash, generated by the PEtab Select library.
+- ``model_id``: The model ID.
+- ``model_subspace_id``: Same as in the model space files.
+- ``model_subspace_indices``: The indices that locate this model in its model subspace.
+- ``parameters``: The parameters from the problem (either values or ``'estimate'``) (a specific combination from a model space file, but uncalibrated).
+- ``petab_yaml``: Same as in model space files.
+- ``predecessor_model_hash``: The hash of the model that preceded this model during the model selection process.
diff --git a/petab_select/__init__.py b/petab_select/__init__.py
index b2455da3..233d233b 100644
--- a/petab_select/__init__.py
+++ b/petab_select/__init__.py
@@ -15,5 +15,5 @@
 __all__ = [
     x
     for x in dir(sys.modules[__name__])
-    if not x.startswith('_') and x != 'sys'
+    if not x.startswith("_") and x != "sys"
 ]
diff --git a/petab_select/candidate_space.py b/petab_select/candidate_space.py
index ecd89eaf..865bebf6 100644
--- a/petab_select/candidate_space.py
+++ b/petab_select/candidate_space.py
@@ -1,21 +1,21 @@
 """Classes and methods related to candidate spaces."""
+
 import abc
 import bisect
 import copy
 import csv
 import logging
 import warnings
+from collections.abc import Callable
 from pathlib import Path
-from typing import Any, Callable, Dict, List, Optional, Sequence, Type, Union
+from typing import Any
 
 import numpy as np
 from more_itertools import one
 
 from .constants import (
     ESTIMATE,
-    METHOD,
     METHOD_SCHEME,
-    MODELS,
     NEXT_METHOD,
     PREDECESSOR_MODEL,
     PREVIOUS_METHODS,
@@ -26,15 +26,15 @@
     Method,
 )
 from .handlers import TYPE_LIMIT, LimitHandler
-from .model import Model, default_compare
+from .model import Model, ModelHash, default_compare
 
 __all__ = [
-    'BackwardCandidateSpace',
-    'BruteForceCandidateSpace',
-    'CandidateSpace',
-    'FamosCandidateSpace',
-    'ForwardCandidateSpace',
-    'LateralCandidateSpace',
+    "BackwardCandidateSpace",
+    "BruteForceCandidateSpace",
+    "CandidateSpace",
+    "FamosCandidateSpace",
+    "ForwardCandidateSpace",
+    "LateralCandidateSpace",
 ]
 
 
@@ -45,58 +45,76 @@ class CandidateSpace(abc.ABC):
     space, that will be provided to a model selection method for selection.
 
     Attributes:
+        criterion:
+            The criterion by which models are compared.
         distances:
             The distances of all candidate models from the initial model.
-
-            FIXME(dilpath) change list to int? Is storage of more than one value useful?
         predecessor_model:
             The model used for comparison, e.g. for stepwise methods.
         previous_predecessor_model:
             The previous predecessor model.
-        models:
-            The current set of candidate models.
-        exclusions:
-            A list of model hashes. Models that match a hash in ``exclusions`` will not
-            be accepted into the candidate space. The hashes of models that are accepted
-            are added to ``exclusions``.
-        limit:
-            A handler to limit the number of accepted models.
-        method:
-            The model selection method of the candidate space.
+        excluded_hashes:
+            A list of model hashes that will not be accepted into the candidate
+            space. The hashes of accepted models are added to :attr:``excluded_hashes``.
         governing_method:
             Used to store the search method that governs the choice of method during
             a search. In some cases, this is always the same as the method attribute.
             An example of a difference is in the bidirectional method, where ``governing_method``
             stores the bidirectional method, whereas `method` may also store the forward or
             backward methods.
+        limit:
+            A handler to limit the number of accepted models.
+        models:
+            The current set of candidate models.
+        method:
+            The model selection method of the candidate space.
+        predecessor_model:
+            The model used for comparison, e.g. for stepwise methods.
+        previous_predecessor_model:
+            The previous predecessor model.
+        retry_model_space_search_if_no_models:
+            Whether a search with a candidate space should be repeated upon failure.
+            Useful for the :class:`BidirectionalCandidateSpace`, which switches directions
+            upon failure.
         summary_tsv:
             A string or :class:`pathlib.Path`. A summary of the model selection progress
             will be written to this file.
+        calibrated_models:
+            All models that have been calibrated across all iterations of model
+            selection.
+        latest_iteration_calibrated_models:
+            The calibrated models from the most recent iteration.
+    """
 
+    """
     FIXME(dilpath)
-    #limited:
-    #    A descriptor that handles the limit on the number of accepted models.
-    #limit:
-    #    Models will fail `self.consider` if `len(self.models) >= limit`.
+    - rename previous_predecessor_model to latest_predecessor_model
+    - distances: change list to int? Is storage of more than one value useful?
     """
 
     def __init__(
         self,
         method: Method,
+        criterion: Criterion,
         # TODO add MODEL_TYPE = Union[str, Model], str for VIRTUAL_INITIAL_MODEL
-        predecessor_model: Optional[Model] = None,
-        exclusions: Optional[List[Any]] = None,
+        predecessor_model: Model | None = None,
+        excluded_hashes: list[ModelHash] | None = None,
         limit: TYPE_LIMIT = np.inf,
         summary_tsv: TYPE_PATH = None,
-        previous_predecessor_model: Optional[Model] = None,
+        previous_predecessor_model: Model | None = None,
+        calibrated_models: dict[ModelHash, Model] = None,
     ):
+        """See class attributes for arguments."""
         self.method = method
 
         self.limit = LimitHandler(
             current=self.n_accepted,
             limit=limit,
         )
-        self.reset(predecessor_model=predecessor_model, exclusions=exclusions)
+        self.reset(
+            predecessor_model=predecessor_model,
+            excluded_hashes=excluded_hashes,
+        )
 
         self.summary_tsv = summary_tsv
         if self.summary_tsv is not None:
@@ -107,7 +125,107 @@ def __init__(
         if self.previous_predecessor_model is None:
             self.previous_predecessor_model = self.predecessor_model
 
-    def write_summary_tsv(self, row):
+        self.set_iteration_user_calibrated_models({})
+        self.criterion = criterion
+        self.calibrated_models = calibrated_models
+        if self.calibrated_models is None:
+            self.calibrated_models = {}
+        self.latest_iteration_calibrated_models = {}
+
+    def set_iteration_user_calibrated_models(
+        self, user_calibrated_models: dict[str, Model] | None
+    ) -> None:
+        """Hide previously-calibrated models from the calibration tool.
+
+        This allows previously-calibrated model results, e.g. from a previous
+        model selection job, to be re-used in this job. Calibrated models are
+        stored here between model selection iterations, while the calibration
+        tool calibrates the uncalibrated models of the iteration. The models
+        are then combined as the full model calibration results for the
+        iteration, with :func:`get_iteration_calibrated_models`.
+
+        Args:
+            user_calibrated_models:
+                The previously-calibrated models. Keys are model hashes, values
+                are models.
+        """
+        if not user_calibrated_models:
+            self.iteration_user_calibrated_models = {}
+            return
+
+        iteration_uncalibrated_models = []
+        iteration_user_calibrated_models = {}
+        for model in self.models:
+            if (
+                (user_model := user_calibrated_models.get(model.get_hash()))
+                is not None
+            ) and (
+                user_model.get_criterion(
+                    self.criterion, raise_on_failure=False
+                )
+                is not None
+            ):
+                logging.info(
+                    f"Using user-supplied result for: {model.get_hash()}"
+                )
+                user_model_copy = copy.deepcopy(user_model)
+                user_model_copy.predecessor_model_hash = (
+                    self.predecessor_model.get_hash()
+                    if isinstance(self.predecessor_model, Model)
+                    else self.predecessor_model
+                )
+                iteration_user_calibrated_models[
+                    user_model_copy.get_hash()
+                ] = user_model_copy
+            else:
+                iteration_uncalibrated_models.append(model)
+        self.iteration_user_calibrated_models = (
+            iteration_user_calibrated_models
+        )
+        self.models = iteration_uncalibrated_models
+
+    def get_iteration_calibrated_models(
+        self, calibrated_models: dict[str, Model], reset: bool = False
+    ) -> dict[str, Model]:
+        """Get the full list of calibrated models for the current iteration.
+
+        The full list of models identified for calibration in an iteration of
+        model selection may include models for which calibration results are
+        already available. This combines the calibration results of the
+        uncalibrated models, with the models that were already calibrated, to
+        produce the full list of models that were identified for calibration
+        in the current iteration.
+
+        Args:
+            calibrated_models:
+                The calibration results for the uncalibrated models of this
+                iteration. Keys are model hashes, values are models.
+            reset:
+                Whether to remove the previously calibrated models from the
+                candidate space, after they are used to produce the full list
+                of calibrated models.
+
+        Returns:
+            The full list of calibrated models.
+        """
+        combined_calibrated_models = (
+            self.iteration_user_calibrated_models | calibrated_models
+        )
+        if reset:
+            self.set_iteration_user_calibrated_models(
+                user_calibrated_models={}
+            )
+        return combined_calibrated_models
+
+    def write_summary_tsv(self, row: list[Any]):
+        """Write the summary of the last iteration to a TSV file.
+
+        The destination is defined in ``self.summary_tsv``.
+
+        Args:
+            row:
+                The row that will be written to disk.
+        """
         if self.summary_tsv is None:
             return
 
@@ -115,30 +233,44 @@ def write_summary_tsv(self, row):
         if not isinstance(row, list):
             row = [
                 row,
-                *([''] * 5),
+                *([""] * 5),
             ]
 
-        with open(self.summary_tsv, 'a', encoding="utf-8") as f:
-            writer = csv.writer(f, delimiter='\t')
+        with open(self.summary_tsv, "a", encoding="utf-8") as f:
+            writer = csv.writer(f, delimiter="\t")
             writer.writerow(row)
 
     def _setup_summary_tsv(self):
+        """Setup the summary TSV file columns."""
         self.summary_tsv.resolve().parent.mkdir(parents=True, exist_ok=True)
 
         if not self.summary_tsv.exists():
             self.write_summary_tsv(
                 [
-                    'method',
-                    '# candidates',
-                    'predecessor change',
-                    'current model criterion',
-                    'current model',
-                    'candidate changes',
+                    "method",
+                    "# candidates",
+                    "predecessor change",
+                    "current model criterion",
+                    "current model",
+                    "candidate changes",
                 ]
             )
 
     @classmethod
-    def read_arguments_from_yaml_dict(cls, yaml_dict):
+    def read_arguments_from_yaml_dict(
+        cls,
+        yaml_dict: dict[str, str],
+    ) -> dict[str, str | Model]:
+        """Parse settings that were stored in YAML.
+
+        Args:
+            yaml_dict:
+                The information that was read from the YAML file. Keys are
+                class attributes, values are the corresponding values.
+
+        Returns:
+            The settings, parsed into PEtab Select objects where possible.
+        """
         kwargs = copy.deepcopy(yaml_dict)
 
         predecessor_model = None
@@ -173,26 +305,23 @@ def is_plausible(self, model: Model) -> bool:
         """
         return True
 
-    def distance(self, model: Model) -> Union[None, float, int]:
+    def distance(self, model: Model) -> None | float | int:
         """Compute the distance between two models that are neighbors.
 
         Args:
             model:
                 The candidate model.
-            predecessor_model:
-                The initial model.
 
         Returns:
-            The distance from ``predecessor_model`` to ``model``, or ``None`` if the
-            distance should not be computed.
+            The distance from ``self.predecessor_model`` to ``model``, or
+            ``None`` if the distance should not be computed.
         """
         return None
 
     def accept(
         self,
         model: Model,
-        distance: Union[None, float, int],
-        # keep_others: bool = True,
+        distance: None | float | int,
     ) -> None:
         """Add a candidate model to the candidate space.
 
@@ -201,11 +330,6 @@ def accept(
                 The model that will be added.
             distance:
                 The distance of the model from the predecessor model.
-
-        FIXME(dilpath)
-        #keep_others:
-        #    Whether to keep other models that were previously added to the
-        #    candidate space.
         """
         model.predecessor_model_hash = (
             self.predecessor_model.get_hash()
@@ -214,46 +338,35 @@ def accept(
         )
         self.models.append(model)
         self.distances.append(distance)
-        self.exclude(model)
+        self.set_excluded_hashes(model, extend=True)
+
+    def n_accepted(self) -> int:
+        """Get the current number of accepted models.
 
-    def n_accepted(self) -> TYPE_LIMIT:
-        """Get the current number of accepted models."""
+        Returns:
+            The number of models.
+        """
         return len(self.models)
 
-    def exclude(
+    def excluded(
         self,
-        model: Union[Model, List[Model]],
-    ) -> None:
-        """Exclude model(s) from future consideration.
+        model_hash: Model | ModelHash,
+    ) -> bool:
+        """Check whether a model is excluded.
 
         Args:
             model:
-                The model(s) that will be excluded.
-        """
-        if isinstance(model, list):
-            for _model in model:
-                self.exclusions.append(_model.get_hash())
-        else:
-            self.exclusions.append(model.get_hash())
-
-    def exclude_hashes(self, hashes: Sequence[str]) -> None:
-        """Exclude models from future consideration, by hash.
+                The model.
 
-        Args:
-            hashes:
-                The model hashes that will be excluded
+        Returns:
+            ``True`` if the ``model`` is excluded, otherwise ``False``.
         """
-        self.exclusions.extend(hashes)
-
-    def excluded(
-        self,
-        model: Model,
-    ) -> bool:
-        """Whether a model is excluded."""
-        return model.get_hash() in self.exclusions
+        if isinstance(model_hash, Model):
+            model_hash = model_hash.get_hash()
+        return model_hash in self.get_excluded_hashes()
 
     @abc.abstractmethod
-    def _consider_method(self, model) -> bool:
+    def _consider_method(self, model: Model) -> bool:
         """Consider whether a model should be accepted, according to a method.
 
         Args:
@@ -265,7 +378,7 @@ def _consider_method(self, model) -> bool:
         """
         return True
 
-    def consider(self, model: Union[Model, None]) -> bool:
+    def consider(self, model: Model | None) -> bool:
         """Add a candidate model, if it should be added.
 
         Args:
@@ -277,11 +390,10 @@ def consider(self, model: Union[Model, None]) -> bool:
             Whether it is OK to send additional models to the candidate space. For
             example, if the limit of the number of accepted models has been reached,
             then no further models should be sent.
-
-            FIXME(dilpath)
-            TODO change to return whether the model was accepted, and instead add
-            `self.continue` to determine whether additional models should be sent.
         """
+        # FIXME(dilpath)
+        # TODO change to return whether the model was accepted, and instead add
+        # `self.continue` to determine whether additional models should be sent.
         # Model was excluded by the `ModelSubspace` that called this method, so can be
         # skipped.
         if model is None:
@@ -291,8 +403,10 @@ def consider(self, model: Union[Model, None]) -> bool:
             return False
         if self.excluded(model):
             warnings.warn(
-                f'Model has been previously excluded from the candidate space so is skipped here. Model subspace ID: {model.model_subspace_id}. Parameterization: {model.parameters}',
+                f"Model `{model.get_hash()}` has been previously excluded "
+                "from the candidate space so is skipped here.",
                 RuntimeWarning,
+                stacklevel=2,
             )
             return True
         if not self.is_plausible(model):
@@ -307,46 +421,92 @@ def reset_accepted(self) -> None:
         self.models = []
         self.distances = []
 
-    def set_predecessor_model(
-        self, predecessor_model: Union[Model, str, None]
-    ):
+    def set_predecessor_model(self, predecessor_model: Model | str | None):
+        """Set the predecessor model.
+
+        See class attributes for arguments.
+        """
         self.predecessor_model = predecessor_model
         if (
             self.predecessor_model == VIRTUAL_INITIAL_MODEL
             and self.method not in VIRTUAL_INITIAL_MODEL_METHODS
         ):
             raise ValueError(
-                f'A virtual initial model was requested for a method ({self.method}) that does not support them.'
+                f"A virtual initial model was requested for a method ({self.method}) that does not support them."
             )
 
-    def get_predecessor_model(self):
+    def get_predecessor_model(self) -> str | Model:
+        """Get the predecessor model."""
         return self.predecessor_model
 
-    def set_exclusions(self, exclusions: Union[List[str], None]):
-        # TODO change to List[str] for hashes?
-        self.exclusions = exclusions
-        if self.exclusions is None:
-            self.exclusions = []
+    def set_excluded_hashes(
+        self,
+        hashes: Model | ModelHash | list[Model | ModelHash],
+        extend: bool = False,
+    ) -> None:
+        """Set the excluded hashes.
+
+        Args:
+            hashes:
+                The model hashes that will be excluded.
+            extend:
+                Whether to replace or extend the current excluded hashes.
+        """
+        if isinstance(hashes, Model | ModelHash):
+            hashes = [hashes]
+        excluded_hashes = set()
+        for potential_hash in hashes:
+            if isinstance(potential_hash, Model):
+                potential_hash = potential_hash.get_hash()
+            excluded_hashes.add(potential_hash)
+
+        if extend:
+            self.excluded_hashes = self.get_excluded_hashes() | excluded_hashes
+        else:
+            self.excluded_hashes = set(excluded_hashes)
+
+    def get_excluded_hashes(self) -> set[ModelHash]:
+        """Get the excluded hashes.
+
+        Returns:
+            The hashes of excluded models.
+        """
+        try:
+            return self.excluded_hashes
+        except AttributeError:
+            self.excluded_hashes = set()
+            return self.get_excluded_hashes()
 
-    def get_exclusions(self):
-        return self.exclusions
+    def set_limit(self, limit: TYPE_LIMIT = None) -> None:
+        """Set the limit on the number of accepted models.
 
-    def set_limit(self, limit: TYPE_LIMIT = None):
+        Args:
+            limit:
+                The limit.
+        """
         if limit is not None:
             self.limit.set_limit(limit)
 
-    def get_limit(self):
+    def get_limit(self) -> TYPE_LIMIT:
+        """Get the limit on the number of accepted models."""
         return self.limit.get_limit()
 
-    def wrap_search_subspaces(self, search_subspaces: Callable[[], None]):
+    def wrap_search_subspaces(
+        self,
+        search_subspaces: Callable[[], None],
+    ) -> Callable:
         """Decorate the subspace searches of a model space.
 
         Used by candidate spaces to perform changes that alter the search.
-        See :class:`BidirectionalCandidateSpace` for an example, where it's used to switch directions.
+        See :class:`BidirectionalCandidateSpace` for an example, where it's
+        used to switch directions.
 
         Args:
             search_subspaces:
                 The method that searches the subspaces of a model space.
+
+        Returns:
+            The wrapped ``search_subspaces``.
         """
 
         def wrapper():
@@ -356,9 +516,9 @@ def wrapper():
 
     def reset(
         self,
-        predecessor_model: Optional[Union[Model, str, None]] = None,
+        predecessor_model: Model | str | None | None = None,
         # FIXME change `Any` to some `TYPE_MODEL_HASH` (e.g. union of str/int/float)
-        exclusions: Optional[Union[List[str], None]] = None,
+        excluded_hashes: list[ModelHash] | None = None,
         limit: TYPE_LIMIT = None,
     ) -> None:
         """Reset the candidate models, optionally reinitialize with a model.
@@ -366,21 +526,30 @@ def reset(
         Args:
             predecessor_model:
                 The initial model.
-            exclusions:
-                Whether to reset model exclusions.
+            excluded_hashes:
+                These hashes will extend the current excluded hashes.
             limit:
-                The new upper limit of the number of models in this candidate space.
+                The new upper limit of the number of models in this candidate
+                space. Defaults to the previous limit.
         """
+        if excluded_hashes is None:
+            excluded_hashes = self.get_excluded_hashes()
+        if limit is None:
+            limit = self.limit.get_limit()
+
         self.set_predecessor_model(predecessor_model)
         self.reset_accepted()
-        self.set_exclusions(exclusions)
+        self.set_excluded_hashes(
+            excluded_hashes,
+            extend=False,  # FIXME True?
+        )
         self.set_limit(limit)
 
     def distances_in_estimated_parameters(
         self,
         model: Model,
-        predecessor_model: Optional[Model] = None,
-    ) -> Dict[str, Union[float, int]]:
+        predecessor_model: Model | None = None,
+    ) -> dict[str, float | int]:
         """Distance between two models in model space, using different metrics.
 
         All metrics are in terms of estimated parameters.
@@ -395,10 +564,13 @@ def distances_in_estimated_parameters(
         Args:
             model:
                 The candidate model.
+            predecessor_model:
+                See class attributes.
 
         Returns:
-            The distances between the models, as a dictionary, where a key is the
-            name of the metric, and the value is the corresponding distance.
+            The distances between the models, as a dictionary, where a key is
+            the name of the metric, and the value is the corresponding
+            distance.
         """
         model0 = predecessor_model
         if model0 is None:
@@ -409,34 +581,40 @@ def distances_in_estimated_parameters(
             model0.petab_yaml
         ):
             raise NotImplementedError(
-                'Computation of distances between different PEtab problems is '
-                'currently not supported. This error is also raised if the same '
-                'PEtab problem is read from YAML files in different locations.'
+                "Computation of distances between different PEtab problems is "
+                "currently not supported. This error is also raised if the same "
+                "PEtab problem is read from YAML files in different locations."
             )
 
         # All parameters from the PEtab problem are used in the computation.
         if model0 == VIRTUAL_INITIAL_MODEL:
             parameter_ids = list(model1.petab_parameters)
-            # FIXME need to take superset of all parameters amongst all PEtab problems
-            # in all model subspaces to get an accurate comparable distance. Currently
-            # only reasonable when working with a single PEtab problem for all models
-            # in all subspaces.
             if self.method == Method.FORWARD:
                 parameters0 = np.array([0 for _ in parameter_ids])
             elif self.method == Method.BACKWARD:
                 parameters0 = np.array([ESTIMATE for _ in parameter_ids])
             else:
                 raise NotImplementedError(
-                    'Distances for the virtual initial model have not yet been '
+                    "Distances for the virtual initial model have not yet been "
                     f'implemented for the method "{self.method}". Please notify the'
-                    'developers.'
+                    "developers."
                 )
         else:
             parameter_ids = list(model0.petab_parameters)
             parameters0 = np.array(
                 model0.get_parameter_values(parameter_ids=parameter_ids)
             )
-
+            # FIXME need to take superset of all parameters amongst all PEtab problems
+            # in all model subspaces to get an accurate comparable distance. Currently
+            # only reasonable when working with a single PEtab problem for all models
+            # in all subspaces.
+            if model0.petab_yaml.resolve() != model1.petab_yaml.resolve():
+                raise ValueError(
+                    "Computing the distance between different models that "
+                    'have different "base" PEtab problems is not yet '
+                    f"supported. First base PEtab problem: {model0.petab_yaml}."
+                    f" Second base PEtab problem: {model1.petab_yaml}."
+                )
         parameters1 = np.array(
             model1.get_parameter_values(parameter_ids=parameter_ids)
         )
@@ -453,10 +631,10 @@ def distances_in_estimated_parameters(
         # Change in the number of estimated parameters.
         size = np.sum(difference)
 
-        # TODO constants?
+        # TODO constants? e.g. Distance.L1 and Distance.Size
         distances = {
-            'l1': l1,
-            'size': size,
+            "l1": l1,
+            "size": size,
         }
 
         return distances
@@ -464,6 +642,7 @@ def distances_in_estimated_parameters(
     def update_after_calibration(
         self,
         *args,
+        iteration_calibrated_models: dict[ModelHash, Model],
         **kwargs,
     ):
         """Do work in the candidate space after calibration.
@@ -475,7 +654,12 @@ def update_after_calibration(
         are here, to ensure candidate spaces can be switched easily and still
         receive sufficient arguments.
         """
-        pass
+        self.calibrated_models |= iteration_calibrated_models
+        self.latest_iteration_calibrated_models = iteration_calibrated_models
+        self.set_excluded_hashes(
+            self.latest_iteration_calibrated_models,
+            extend=False,  # FIXME True?
+        )
 
 
 class ForwardCandidateSpace(CandidateSpace):
@@ -494,7 +678,7 @@ class ForwardCandidateSpace(CandidateSpace):
     def __init__(
         self,
         *args,
-        predecessor_model: Optional[Union[Model, str]] = None,
+        predecessor_model: Model | str | None = None,
         max_steps: int = None,
         **kwargs,
     ):
@@ -505,15 +689,15 @@ def __init__(
         if predecessor_model is None:
             predecessor_model = VIRTUAL_INITIAL_MODEL
         super().__init__(
-            method=Method.FORWARD if self.direction == 1 else Method.BACKWARD,
             *args,
+            method=Method.FORWARD if self.direction == 1 else Method.BACKWARD,
             predecessor_model=predecessor_model,
             **kwargs,
         )
 
     def is_plausible(self, model: Model) -> bool:
         distances = self.distances_in_estimated_parameters(model)
-        n_steps = self.direction * distances['size']
+        n_steps = self.direction * distances["size"]
 
         if self.max_steps is not None and n_steps > self.max_steps:
             raise StopIteration(
@@ -524,7 +708,7 @@ def is_plausible(self, model: Model) -> bool:
         # increases (or decreases, if `self.direction == -1`), and no
         # previously estimated parameters become fixed.
         if self.predecessor_model == VIRTUAL_INITIAL_MODEL or (
-            n_steps > 0 and distances['l1'] == n_steps
+            n_steps > 0 and distances["l1"] == n_steps
         ):
             return True
         return False
@@ -533,7 +717,7 @@ def distance(self, model: Model) -> int:
         # TODO calculated here and `is_plausible`. Rewrite to only calculate
         #      once?
         distances = self.distances_in_estimated_parameters(model)
-        return distances['l1']
+        return distances["l1"]
 
     def _consider_method(self, model) -> bool:
         """See :meth:`CandidateSpace._consider_method`."""
@@ -561,6 +745,67 @@ class BackwardCandidateSpace(ForwardCandidateSpace):
     direction = -1
 
 
+def forward_super_and_inner(
+    candidate_space: CandidateSpace, method_name: str
+) -> None:
+    """Decorator to call the method of both the `super()` and `inner` space.
+
+    Useful in the :class:`FamosCandidateSpace`, where e.g. excluded hashes
+    should be applied to both the governing `FamosCandidateSpace`, and the
+    active `FamosCandidateSpace.inner_candidate_space`.
+
+    If the method is an instance member, `candidate_space` will be used instead
+    of `super()`.
+
+    Args:
+        candidate_space:
+            A candidate space that contains an `inner_candidate_space`.
+        method_name:
+            The method of the `candidate_space` to decorate.
+
+    Returns:
+        A tuple with the output from the method called with
+            1. `super()` if possible else `candidate_space`, and
+            2. `candidate_space.inner_candidate_space`.
+    """
+
+    # TODO check if docs look OK for wrapped methods; try functools.wraps
+    # @wraps(getattr(candidate_space, method_name))
+    def wrapped_method(*args, **kwargs):
+        try:
+            super_object = super(type(candidate_space), candidate_space)
+            getattr(super_object, method_name)
+        except AttributeError:
+            super_object = candidate_space
+        inner_object = candidate_space.inner_candidate_space
+
+        return (
+            getattr(super_object, method_name)(*args, **kwargs),
+            getattr(inner_object, method_name)(*args, **kwargs),
+        )
+
+    return wrapped_method
+
+
+def forward_inner(candidate_space: CandidateSpace, method_name: str) -> None:
+    """Decorator to call the method of the `inner` space.
+
+    See :func:`super_and_inner` for more details.
+
+    Returns:
+        The output from the method called with
+        `candidate_space.inner_candidate_space`.
+    """
+
+    # TODO check if docs look OK for wrapped methods; try functools.wraps
+    # @wraps(getattr(candidate_space, method_name))
+    def wrapped_method(*args, **kwargs):
+        inner_object = candidate_space.inner_candidate_space
+        return getattr(inner_object, method_name)(*args, **kwargs)
+
+    return wrapped_method
+
+
 class FamosCandidateSpace(CandidateSpace):
     """The FAMoS method class.
 
@@ -608,23 +853,41 @@ class FamosCandidateSpace(CandidateSpace):
         None: Method.FORWARD,
     }
 
+    forwarded_inner = [
+        "_consider_method",
+    ]
+    _consider_method = None
+    forwarded_super_and_inner = [
+        "reset_accepted",
+        "set_predecessor_model",
+        "set_excluded_hashes",
+        "set_limit",
+    ]
+
     def __init__(
         self,
         *args,
-        predecessor_model: Optional[Union[Model, str, None]] = None,
-        critical_parameter_sets: List = [],
-        swap_parameter_sets: List = [],
-        method_scheme: Dict[tuple, str] = None,
+        predecessor_model: Model | str | None | None = None,
+        critical_parameter_sets: list = [],
+        swap_parameter_sets: list = [],
+        method_scheme: dict[tuple, str] = None,
         n_reattempts: int = 0,
         consecutive_laterals: bool = False,
         **kwargs,
     ):
+        for method_name in FamosCandidateSpace.forwarded_inner:
+            setattr(self, method_name, forward_inner(self, method_name))
+        for method_name in FamosCandidateSpace.forwarded_super_and_inner:
+            setattr(
+                self, method_name, forward_super_and_inner(self, method_name)
+            )
+
         self.critical_parameter_sets = critical_parameter_sets
         self.swap_parameter_sets = swap_parameter_sets
 
         self.method_scheme = method_scheme
         if method_scheme is None:
-            self.method_scheme = self.default_method_scheme
+            self.method_scheme = FamosCandidateSpace.default_method_scheme
 
         # FIXME remove and use `self.method` everywhere in the constructor
         #       instead -- not required in other methods anymore
@@ -643,7 +906,7 @@ def __init__(
             and not self.check_critical(predecessor_model)
         ):
             raise ValueError(
-                f'Provided predecessor model {predecessor_model.parameters} does not contain necessary critical parameters {self.critical_parameter_sets}. Provide a valid predecessor model.'
+                f"Provided predecessor model {predecessor_model.parameters} does not contain necessary critical parameters {self.critical_parameter_sets}. Provide a valid predecessor model."
             )
 
         if (
@@ -657,22 +920,20 @@ def __init__(
         # FIXME remove `None` from the resulting `inner_methods` set?
         inner_methods = set.union(
             *[
-                set(
-                    [
-                        *(
-                            method_pattern
-                            if method_pattern is not None
-                            else (None,)
-                        ),
-                        next_method,
-                    ]
-                )
+                {
+                    *(
+                        method_pattern
+                        if method_pattern is not None
+                        else (None,)
+                    ),
+                    next_method,
+                }
                 for method_pattern, next_method in self.method_scheme.items()
             ]
         )
         if Method.LATERAL in inner_methods and not self.swap_parameter_sets:
             raise ValueError(
-                f"Use of the lateral method with FAMoS requires `swap_parameter_sets`."
+                "Use of the lateral method with FAMoS requires `swap_parameter_sets`."
             )
 
         for method in inner_methods:
@@ -683,8 +944,8 @@ def __init__(
                 Method.MOST_DISTANT,
             ]:
                 raise NotImplementedError(
-                    f'Methods FAMoS can swap to are `Method.FORWARD`, `Method.BACKWARD` and `Method.LATERAL`, not {method}. \
-                    Check if the method_scheme scheme provided is correct.'
+                    f"Methods FAMoS can swap to are `Method.FORWARD`, `Method.BACKWARD` and `Method.LATERAL`, not {method}. \
+                    Check if the method_scheme scheme provided is correct."
                 )
 
         self.inner_candidate_spaces = {
@@ -714,8 +975,8 @@ def __init__(
         ]
 
         super().__init__(
-            method=self.method,
             *args,
+            method=self.method,
             predecessor_model=predecessor_model,
             **kwargs,
         )
@@ -728,7 +989,8 @@ def __init__(
             and (Method.LATERAL,) not in self.method_scheme
         ):
             raise ValueError(
-                "Please provide a method to switch to after a lateral search, if not enabling the `consecutive_laterals` option."
+                "Please provide a method to switch to after a lateral search, "
+                "if not enabling the `consecutive_laterals` option."
             )
 
         if self.n_reattempts:
@@ -768,33 +1030,34 @@ def read_arguments_from_yaml_dict(cls, yaml_dict) -> dict:
     def update_after_calibration(
         self,
         *args,
-        calibrated_models: Dict[str, Model],
-        newly_calibrated_models: Dict[str, Model],
-        criterion: Criterion,
+        iteration_calibrated_models: dict[str, Model],
         **kwargs,
     ) -> None:
         """See `CandidateSpace.update_after_calibration`."""
-
-        # super().update_after_calibration(*args, **kwargs)
+        super().update_after_calibration(
+            *args,
+            iteration_calibrated_models=iteration_calibrated_models,
+            **kwargs,
+        )
 
         # In case we jumped to most distant in the last iteration,
         # there's no need for an update, so we reset the jumped variable
         # to False and continue to candidate generation
         if self.jumped_to_most_distant:
             self.jumped_to_most_distant = False
-            jumped_to_model = one(newly_calibrated_models.values())
+            jumped_to_model = one(iteration_calibrated_models.values())
             self.set_predecessor_model(jumped_to_model)
+            self.previous_predecessor_model = jumped_to_model
             self.best_model_of_current_run = jumped_to_model
             return False
 
-        if self.update_from_newly_calibrated_models(
-            newly_calibrated_models=newly_calibrated_models,
-            criterion=criterion,
+        if self.update_from_iteration_calibrated_models(
+            iteration_calibrated_models=iteration_calibrated_models,
         ):
             logging.info("Switching method")
-            self.switch_method(calibrated_models=calibrated_models)
+            self.switch_method()
             self.switch_inner_candidate_space(
-                exclusions=list(calibrated_models),
+                excluded_hashes=list(self.calibrated_models),
             )
             logging.info(
                 "Method switched to ", self.inner_candidate_space.method
@@ -802,38 +1065,36 @@ def update_after_calibration(
 
         self.method_history.append(self.method)
 
-    def update_from_newly_calibrated_models(
+    def update_from_iteration_calibrated_models(
         self,
-        newly_calibrated_models: Dict[str, Model],
-        criterion: Criterion,
+        iteration_calibrated_models: dict[str, Model],
     ) -> bool:
-        """Update ``self.best_models`` with the latest ``newly_calibrated_models``
+        """Update ``self.best_models`` with the latest ``iteration_calibrated_models``
         and determine if there was a new best model. If so, return
-        ``False``. ``True`` otherwise."""
-
+        ``False``. ``True`` otherwise.
+        """
         go_into_switch_method = True
-        for newly_calibrated_model in newly_calibrated_models.values():
+        for model in iteration_calibrated_models.values():
             if (
                 self.best_model_of_current_run == VIRTUAL_INITIAL_MODEL
                 or default_compare(
                     model0=self.best_model_of_current_run,
-                    model1=newly_calibrated_model,
-                    criterion=criterion,
+                    model1=model,
+                    criterion=self.criterion,
                 )
             ):
                 go_into_switch_method = False
-                self.best_model_of_current_run = newly_calibrated_model
+                self.best_model_of_current_run = model
 
             if len(
                 self.best_models
             ) < self.most_distant_max_number or default_compare(
                 model0=self.best_models[self.most_distant_max_number - 1],
-                model1=newly_calibrated_model,
-                criterion=criterion,
+                model1=model,
+                criterion=self.criterion,
             ):
                 self.insert_model_into_best_models(
-                    model_to_insert=newly_calibrated_model,
-                    criterion=criterion,
+                    model_to_insert=model,
                 )
 
         self.best_models = self.best_models[: self.most_distant_max_number]
@@ -847,22 +1108,24 @@ def update_from_newly_calibrated_models(
             go_into_switch_method = True
         return go_into_switch_method
 
-    def insert_model_into_best_models(
-        self, model_to_insert: Model, criterion: Criterion
-    ) -> None:
+    def insert_model_into_best_models(self, model_to_insert: Model) -> None:
         """Inserts a model into the list of best_models which are sorted
-        w.r.t. the criterion specified."""
+        w.r.t. the criterion specified.
+        """
         insert_index = bisect.bisect_left(
-            [model.get_criterion(criterion) for model in self.best_models],
-            model_to_insert.get_criterion(criterion),
+            [
+                model.get_criterion(self.criterion)
+                for model in self.best_models
+            ],
+            model_to_insert.get_criterion(self.criterion),
         )
         self.best_models.insert(insert_index, model_to_insert)
 
-    def consider(self, model: Union[Model, None]) -> bool:
+    def consider(self, model: Model | None) -> bool:
         """Re-define ``consider`` of FAMoS to be the ``consider`` method
         of the ``inner_candidate_space``. Update all the attributes
-        changed in the ``consider`` method."""
-
+        changed in the ``consider`` method.
+        """
         if self.limit.reached():
             return False
 
@@ -878,48 +1141,14 @@ def consider(self, model: Union[Model, None]) -> bool:
             # update the attributes
             self.models = self.inner_candidate_space.models
             self.distances = self.inner_candidate_space.distances
-            self.exclusions = self.inner_candidate_space.exclusions
+            self.set_excluded_hashes(
+                self.inner_candidate_space.get_excluded_hashes()
+            )
 
             return return_value
 
         return True
 
-    def _consider_method(self, model) -> bool:
-        """See :meth:`CandidateSpace._consider_method`."""
-        return self.inner_candidate_space._consider_method(model)
-
-    def reset_accepted(self) -> None:
-        """Changing the reset_accepted to reset the
-        ``inner_candidate_space`` as well."""
-        super().reset_accepted()
-        self.inner_candidate_space.reset_accepted()
-
-    def set_predecessor_model(
-        self, predecessor_model: Union[Model, str, None]
-    ):
-        """Setting the predecessor model for the
-        ``inner_candidate_space`` as well."""
-        super().set_predecessor_model(predecessor_model=predecessor_model)
-        self.inner_candidate_space.set_predecessor_model(
-            predecessor_model=predecessor_model
-        )
-
-    def set_exclusions(self, exclusions: Union[List[str], None]):
-        """Setting the exclusions for the
-        ``inner_candidate_space`` as well."""
-        self.exclusions = exclusions
-        self.inner_candidate_space.exclusions = exclusions
-        if self.exclusions is None:
-            self.exclusions = []
-            self.inner_candidate_space.exclusions = []
-
-    def set_limit(self, limit: TYPE_LIMIT = None):
-        """Setting the limit for the
-        ``inner_candidate_space`` as well."""
-        if limit is not None:
-            self.limit.set_limit(limit)
-            self.inner_candidate_space.limit.set_limit(limit)
-
     def is_plausible(self, model: Model) -> bool:
         if self.check_critical(model):
             return self.inner_candidate_space.is_plausible(model)
@@ -927,7 +1156,8 @@ def is_plausible(self, model: Model) -> bool:
 
     def check_swap(self, model: Model) -> bool:
         """Check if parameters that are swapped are contained in the
-        same swap parameter set."""
+        same swap parameter set.
+        """
         if self.method != Method.LATERAL:
             return True
 
@@ -947,7 +1177,6 @@ def check_swap(self, model: Model) -> bool:
 
     def check_critical(self, model: Model) -> bool:
         """Check if the model contains all necessary critical parameters"""
-
         estimated_parameters_ids = set(model.get_estimated_parameter_ids_all())
         for critical_set in self.critical_parameter_sets:
             if not estimated_parameters_ids.intersection(set(critical_set)):
@@ -956,11 +1185,10 @@ def check_critical(self, model: Model) -> bool:
 
     def switch_method(
         self,
-        calibrated_models: Dict[str, Model],
     ) -> None:
         """Switch to the next method with respect to the history
-        of methods used and the switching scheme in ``self.method_scheme``."""
-
+        of methods used and the switching scheme in ``self.method_scheme``.
+        """
         previous_method = self.method
         next_method = previous_method
         logging.info("SWITCHING", self.method_history)
@@ -991,7 +1219,7 @@ def switch_method(
         # Terminate if next method is `None`
         if next_method is None:
             if self.n_reattempts:
-                self.jump_to_most_distant(calibrated_models=calibrated_models)
+                self.jump_to_most_distant()
                 return
             raise StopIteration(
                 f"The next method is {next_method}. The search is terminating."
@@ -1008,7 +1236,7 @@ def switch_method(
             and not self.swap_parameter_sets
         ):
             if self.n_reattempts:
-                self.jump_to_most_distant(calibrated_models=calibrated_models)
+                self.jump_to_most_distant()
                 return
             raise ValueError(
                 f"The next method is {next_method}, but there are no critical or swap parameters sets. Terminating."
@@ -1019,39 +1247,34 @@ def switch_method(
 
     def update_method(self, method: Method):
         """Update ``self.method`` to ``method``."""
-
         self.method = method
 
     def switch_inner_candidate_space(
         self,
-        exclusions: List[str],
+        excluded_hashes: list[ModelHash],
     ):
         """Switch the inner candidate space to match the current method.
 
         Args:
-            exclusions:
+            excluded_hashes:
                 Hashes of excluded models.
         """
-
         # if self.method != Method.MOST_DISTANT:
         self.inner_candidate_space = self.inner_candidate_spaces[self.method]
         # reset the next inner candidate space with the current history of all
         # calibrated models
         self.inner_candidate_space.reset(
             predecessor_model=self.predecessor_model,
-            exclusions=exclusions,
+            excluded_hashes=excluded_hashes,
         )
 
     def jump_to_most_distant(
         self,
-        calibrated_models: Dict[str, Model],
     ):
         """Jump to most distant model with respect to the history of all
-        calibrated models."""
-
-        predecessor_model = self.get_most_distant(
-            calibrated_models=calibrated_models,
-        )
+        calibrated models.
+        """
+        predecessor_model = self.get_most_distant()
 
         logging.info("JUMPING: ", predecessor_model.parameters)
 
@@ -1079,7 +1302,6 @@ def jump_to_most_distant(
     # TODO Fix for non-famos model subspaces. FAMOS easy because of only 0;ESTIMATE
     def get_most_distant(
         self,
-        calibrated_models: Dict[str, Model],
     ) -> Model:
         """
         Get most distant model to all the checked models. We take models from the
@@ -1094,7 +1316,6 @@ def get_most_distant(
         If not we choose the model in a subspace that has least distance to this
         complement model.
         """
-
         most_distance = 0
         most_distant_indices = []
 
@@ -1113,7 +1334,7 @@ def get_most_distant(
             # initialize the least distance to the maximal possible value of it
             complement_least_distance = len(complement_parameters)
             # get the complement least distance
-            for calibrated_model in calibrated_models.values():
+            for calibrated_model in self.calibrated_models.values():
                 calibrated_model_estimated_parameters = np.array(
                     [
                         p == ESTIMATE
@@ -1141,15 +1362,12 @@ def get_most_distant(
             raise StopIteration("No most_distant model found. Terminating")
 
         most_distant_parameter_values = [
-            str(index).replace('1', ESTIMATE) for index in most_distant_indices
+            str(index).replace("1", ESTIMATE) for index in most_distant_indices
         ]
 
-        most_distant_parameters = {
-            parameter_id: index
-            for parameter_id, index in zip(
-                parameter_ids, most_distant_parameter_values
-            )
-        }
+        most_distant_parameters = dict(
+            zip(parameter_ids, most_distant_parameter_values, strict=True)
+        )
 
         most_distant_model = Model(
             petab_yaml=model.petab_yaml,
@@ -1173,7 +1391,7 @@ class LateralCandidateSpace(CandidateSpace):
     def __init__(
         self,
         *args,
-        predecessor_model: Union[Model, None],
+        predecessor_model: Model | None,
         max_steps: int = None,
         **kwargs,
     ):
@@ -1183,8 +1401,8 @@ def __init__(
                 Maximal allowed number of swap moves. If 0 then there is no maximum.
         """
         super().__init__(
-            method=Method.LATERAL,
             *args,
+            method=Method.LATERAL,
             predecessor_model=predecessor_model,
             **kwargs,
         )
@@ -1193,14 +1411,14 @@ def __init__(
     def is_plausible(self, model: Model) -> bool:
         if self.predecessor_model is None:
             raise ValueError(
-                f"The predecessor_model is still None. Provide an appropriate predecessor_model"
+                "The predecessor_model is still None. Provide an appropriate predecessor_model"
             )
 
         distances = self.distances_in_estimated_parameters(model)
 
         # If max_number_of_steps is non-zero and the number of steps made is
         # larger then move is not plausible.
-        if self.max_steps is not None and distances['l1'] > 2 * self.max_steps:
+        if self.max_steps is not None and distances["l1"] > 2 * self.max_steps:
             raise StopIteration(
                 f"Maximal number of steps for method {self.method} exceeded. Stop sending candidate models."
             )
@@ -1209,12 +1427,12 @@ def is_plausible(self, model: Model) -> bool:
         # the same, but some estimated parameters have become fixed and vice
         # versa.
         if (
-            distances['size'] == 0
+            distances["size"] == 0
             and
             # distances['size'] == 0 implies L1 % 2 == 0.
             # FIXME here and elsewhere, deal with models that are equal
             #       except for the values of their fixed parameters.
-            distances['l1'] > 0
+            distances["l1"] > 0
         ):
             return True
         return False
@@ -1235,8 +1453,8 @@ def __init__(self, *args, **kwargs):
         #        'brute force candidate space.'
         #    )
         super().__init__(
-            method=Method.BRUTE_FORCE,
             *args,
+            method=Method.BRUTE_FORCE,
             **kwargs,
         )
 
@@ -1253,7 +1471,7 @@ def _consider_method(self, model):
 }
 
 
-def method_to_candidate_space_class(method: Method) -> Type[CandidateSpace]:
+def method_to_candidate_space_class(method: Method) -> type[CandidateSpace]:
     """Get a candidate space class, given its method name.
 
     Args:
@@ -1267,8 +1485,8 @@ def method_to_candidate_space_class(method: Method) -> Type[CandidateSpace]:
     candidate_space_class = candidate_space_classes.get(method, None)
     if candidate_space_class is None:
         raise NotImplementedError(
-            f'The provided method `{method}` does not correspond to an '
-            'implemented candidate space.'
+            f"The provided method `{method}` does not correspond to an "
+            "implemented candidate space."
         )
     return candidate_space_class
 
diff --git a/petab_select/cli.py b/petab_select/cli.py
index 36f15615..f318205e 100644
--- a/petab_select/cli.py
+++ b/petab_select/cli.py
@@ -1,211 +1,198 @@
 """The PEtab Select command-line interface."""
+
 from pathlib import Path
-from typing import Any, Dict, List
+from typing import Any
 
 import click
 import dill
 import numpy as np
 import pandas as pd
+import yaml
 from more_itertools import one
 
 from . import ui
 from .candidate_space import CandidateSpace
-from .constants import PETAB_YAML
-from .model import Model, models_from_yaml_list, models_to_yaml_list
+from .constants import CANDIDATE_SPACE, MODELS, PETAB_YAML, TERMINATE
+from .model import ModelHash, models_from_yaml_list, models_to_yaml_list
 from .problem import Problem
 
 
-def read_state(filename: str) -> Dict[str, Any]:
-    with open(filename, 'rb') as f:
+def read_state(filename: str) -> dict[str, Any]:
+    with open(filename, "rb") as f:
         state = dill.load(f)
 
-    state['problem'] = dill.loads(state['problem'])
-    state['candidate_space'] = dill.loads(state['candidate_space'])
+    state["problem"] = dill.loads(state["problem"])
+    state["candidate_space"] = dill.loads(state["candidate_space"])
 
     return state
 
 
 def write_state(
-    state: Dict[str, Any],
+    state: dict[str, Any],
     filename: str,
-) -> Dict[str, Any]:
-    with open(filename, 'wb') as f:
+) -> dict[str, Any]:
+    with open(filename, "wb") as f:
         dill.dump(state, f)
 
 
 def get_state(
     problem: Problem,
     candidate_space: CandidateSpace,
-) -> Dict[str, Any]:
+) -> dict[str, Any]:
     state = {
-        'problem': dill.dumps(problem),
-        'candidate_space': dill.dumps(candidate_space),
+        "problem": dill.dumps(problem),
+        "candidate_space": dill.dumps(candidate_space),
     }
     return state
 
 
-def check_state_compatibility(
-    state: Dict[str, Any],
-    problem: Problem,
-    candidate_space: CandidateSpace,
-):
-    # if state['problem'].method != problem.method:
-    #    warnings.warn(
-    #        'The method in the problem loaded from the state does not match '
-    #        'the method specified in either the PEtab Select YAML file or '
-    #        'specified explicitly with this command.'
-    #    )
-    pass
-
-
 @click.group()
 def cli():
     pass
 
 
-@cli.command("candidates")
-@click.option(
-    '--problem',
-    '-p',
-    'problem_yaml',
-    help='The PEtab Select YAML problem file.',
-)
-@click.option(
-    '--state',
-    '-s',
-    'state_filename',
-    type=str,
-    help='The file that stores the state.',
-)
+@cli.command("start_iteration")
 @click.option(
-    '--output',
-    '-o',
-    'output',
-    type=str,
-    help='The file where candidate models will be stored.',
-)
-@click.option(
-    '--method',
-    '-m',
-    'method',
-    type=str,
-    default=None,
-    help='The method used to identify the candidate models. Defaults to the method in the problem YAML.',
+    "--problem",
+    "-p",
+    "problem_yaml",
+    help="The PEtab Select YAML problem file.",
 )
 @click.option(
-    '--previous-predecessor-model',
-    '-P',
-    'previous_predecessor_model_yaml',
+    "--state",
+    "-s",
+    "state_dill",
     type=str,
-    default=None,
-    help='(Optional) The predecessor model used in the previous iteration of model selection.',
+    help="The file that stores the state.",
 )
 @click.option(
-    '--calibrated-models',
-    '-C',
-    'calibrated_models_yamls',
+    "--output-uncalibrated-models",
+    "-u",
+    "uncalibrated_models_yaml",
     type=str,
-    multiple=True,
-    default=None,
-    help='(Optional) Models that have been calibrated.',
+    help="The file where uncalibrated models from this iteration will be stored.",
 )
 @click.option(
-    '--newly-calibrated-models',
-    '-N',
-    'newly_calibrated_models_yamls',
+    "--method",
+    "-m",
+    "method",
     type=str,
-    multiple=True,
     default=None,
-    help=(
-        '(Optional) Models that were calibrated in the most recent iteration.'
-    ),
+    help="The method used to identify the candidate models. Defaults to the method in the problem YAML.",
 )
+# @click.option(
+#    '--previous-predecessor-model',
+#    '-P',
+#    'previous_predecessor_model_yaml',
+#    type=str,
+#    default=None,
+#    help='(Optional) The predecessor model used in the previous iteration of model selection.',
+# )
+# @click.option(
+#    '--calibrated-models',
+#    '-C',
+#    'calibrated_models_yamls',
+#    type=str,
+#    multiple=True,
+#    default=None,
+#    help='(Optional) Models that have been calibrated.',
+# )
+# @click.option(
+#    '--newly-calibrated-models',
+#    '-N',
+#    'newly_calibrated_models_yamls',
+#    type=str,
+#    multiple=True,
+#    default=None,
+#    help=(
+#        '(Optional) Models that were calibrated in the most recent iteration.'
+#    ),
+# )
 @click.option(
-    '--limit',
-    '-l',
-    'limit',
+    "--limit",
+    "-l",
+    "limit",
     type=float,
     default=np.inf,
-    help='(Optional) Limit the number of models in the output.',
+    help="(Optional) Limit the number of models in the output.",
 )
 @click.option(
-    '--limit-sent',
-    '-L',
-    'limit_sent',
+    "--limit-sent",
+    "-L",
+    "limit_sent",
     type=float,
     default=np.inf,
     help=(
-        '(Optional) Limit the number of models sent to the candidate space '
-        '(which are possibly rejected and excluded from the output).'
+        "(Optional) Limit the number of models sent to the candidate space "
+        "(which are possibly rejected and excluded from the output)."
     ),
 )
 @click.option(
-    '--relative-paths/--absolute-paths',
-    'relative_paths',
+    "--relative-paths/--absolute-paths",
+    "relative_paths",
     type=bool,
     default=False,
-    help='Whether to output paths relative to the output file.',
+    help="Whether to output paths relative to the output file.",
 )
 @click.option(
-    '--excluded-models',
-    '-e',
-    'excluded_model_files',
+    "--excluded-models",
+    "-e",
+    "excluded_model_files",
     type=str,
     multiple=True,
     default=None,
-    help='Exclude models in this file.',
+    help="Exclude models in this file.",
 )
 @click.option(
-    '--excluded-model-hashes',
-    '-E',
-    'excluded_model_hash_files',
+    "--excluded-model-hashes",
+    "-E",
+    "excluded_model_hash_files",
     type=str,
     multiple=True,
     default=None,
-    help='Exclude model hashes in this file (one model hash per line).',
+    help="Exclude model hashes in this file (one model hash per line).",
 )
-def candidates(
+def start_iteration(
     problem_yaml: str,
-    state_filename: str,
-    output: str,
+    state_dill: str,
+    uncalibrated_models_yaml: str,
     method: str = None,
-    previous_predecessor_model_yaml: str = None,
+    # previous_predecessor_model_yaml: str = None,
     # best: str = None,
-    calibrated_models_yamls: List[str] = None,
-    newly_calibrated_models_yamls: List[str] = None,
+    # calibrated_models_yamls: List[str] = None,
+    # newly_calibrated_models_yamls: List[str] = None,
     limit: float = np.inf,
     limit_sent: float = np.inf,
     relative_paths: bool = False,
-    excluded_model_files: List[str] = None,
-    excluded_model_hash_files: List[str] = None,
+    excluded_model_files: list[str] = None,
+    excluded_model_hash_files: list[str] = None,
 ) -> None:
     """Search for candidate models in the model space.
 
     Documentation for arguments can be viewed with
-    `petab_select candidates --help`.
+    `petab_select start_iteration --help`.
     """
     problem = Problem.from_yaml(problem_yaml)
     if method is None:
         method = problem.method
 
-    # `petab_select.ui.candidates` uses `petab_select.Problem.method` to generate
-    # the candidate space.
+    # `petab_select.ui.start_iteration` uses `petab_select.Problem.method` to
+    # generate the candidate space.
     problem.method = method
-
     candidate_space = problem.new_candidate_space(limit=limit)
 
     # Setup state
-    if not Path(state_filename).exists():
-        Path(state_filename).parent.mkdir(parents=True, exist_ok=True)
+    if not Path(state_dill).exists():
+        Path(state_dill).parent.mkdir(parents=True, exist_ok=True)
     else:
-        state = read_state(state_filename)
-        check_state_compatibility(
-            state=state,
-            problem=problem,
-            candidate_space=candidate_space,
-        )
-        problem = state['problem']
-        candidate_space = state['candidate_space']
+        state = read_state(state_dill)
+        if state["problem"].method != problem.method:
+            raise NotImplementedError(
+                "Changing method in the middle of a run is currently not "
+                "supported. Delete the state to start with a new method."
+            )
+        problem = state["problem"]
+        candidate_space = state["candidate_space"]
 
     excluded_models = []
     # TODO seems like default is `()`, not `None`...
@@ -217,20 +204,136 @@ def candidates(
     excluded_model_hashes = []
     if excluded_model_hash_files is not None:
         for excluded_model_hash_file in excluded_model_hash_files:
-            with open(excluded_model_hash_file, 'r') as f:
-                excluded_model_hashes += f.read().split('\n')
+            with open(excluded_model_hash_file) as f:
+                excluded_model_hashes += f.read().split("\n")
+
+    excluded_hashes = [
+        excluded_model.get_hash() for excluded_model in excluded_models
+    ]
+    excluded_hashes += [
+        ModelHash.from_hash(hash_str) for hash_str in excluded_model_hashes
+    ]
+
+    # previous_predecessor_model = candidate_space.predecessor_model
+    # if previous_predecessor_model_yaml is not None:
+    #    previous_predecessor_model = Model.from_yaml(
+    #        previous_predecessor_model_yaml
+    #    )
+
+    # # FIXME write single methods to take all models from lists of lists of
+    # #       models recursively
+    # calibrated_models = None
+    # if calibrated_models_yamls:
+    #     calibrated_models = {}
+    #     for calibrated_models_yaml in calibrated_models_yamls:
+    #         calibrated_models.update(
+    #             {
+    #                 model.get_hash(): model
+    #                 for model in models_from_yaml_list(calibrated_models_yaml)
+    #             }
+    #         )
+
+    # newly_calibrated_models = None
+    # if newly_calibrated_models_yamls:
+    #     newly_calibrated_models = {}
+    #     for newly_calibrated_models_yaml in newly_calibrated_models_yamls:
+    #         newly_calibrated_models.update(
+    #             {
+    #                 model.get_hash(): model
+    #                 for model in models_from_yaml_list(
+    #                     newly_calibrated_models_yaml
+    #                 )
+    #             }
+    #         )
+
+    ui.start_iteration(
+        problem=problem,
+        candidate_space=candidate_space,
+        # previous_predecessor_model=previous_predecessor_model,
+        # calibrated_models=calibrated_models,
+        # newly_calibrated_models=newly_calibrated_models,
+        limit=limit,
+        limit_sent=limit_sent,
+        excluded_hashes=excluded_hashes,
+        # excluded_models=excluded_models,
+        # excluded_model_hashes=excluded_model_hashes,
+    )
+
+    # Save state
+    write_state(
+        state=get_state(
+            problem=problem,
+            candidate_space=candidate_space,
+        ),
+        filename=state_dill,
+    )
+
+    # Save candidate models
+    models_to_yaml_list(
+        models=candidate_space.models,
+        output_yaml=uncalibrated_models_yaml,
+        relative_paths=relative_paths,
+    )
 
-    previous_predecessor_model = candidate_space.predecessor_model
-    if previous_predecessor_model_yaml is not None:
-        previous_predecessor_model = Model.from_yaml(
-            previous_predecessor_model_yaml
-        )
 
-    # FIXME write single methods to take all models from lists of lists of
-    #       models recursively
-    calibrated_models = None
-    if calibrated_models_yamls is not None:
-        calibrated_models = {}
+@cli.command("end_iteration")
+@click.option(
+    "--state",
+    "-s",
+    "state_dill",
+    type=str,
+    help="The file that stores the state.",
+)
+@click.option(
+    "--output-models",
+    "-m",
+    "models_yaml",
+    type=str,
+    help="The file where this iteration's calibrated models will be stored.",
+)
+@click.option(
+    "--output-metadata",
+    "-d",
+    "metadata_yaml",
+    type=str,
+    help="The file where this iteration's metadata will be stored.",
+)
+@click.option(
+    "--calibrated-models",
+    "-c",
+    "calibrated_models_yamls",
+    type=str,
+    multiple=True,
+    help=(
+        "The calibration results for the uncalibrated models of this iteration."
+    ),
+)
+@click.option(
+    "--relative-paths/--absolute-paths",
+    "relative_paths",
+    type=bool,
+    default=False,
+    help="Whether to output paths relative to the output file.",
+)
+def end_iteration(
+    state_dill: str,
+    models_yaml: str,
+    metadata_yaml: str,
+    calibrated_models_yamls: list[str] = None,
+    relative_paths: bool = False,
+) -> None:
+    """Finalize a model selection iteration.
+
+    Documentation for arguments can be viewed with
+    `petab_select end_iteration --help`.
+    """
+    # Setup state
+    state = read_state(state_dill)
+    problem = state["problem"]
+    candidate_space = state["candidate_space"]
+
+    calibrated_models = {}
+    if calibrated_models_yamls:
         for calibrated_models_yaml in calibrated_models_yamls:
             calibrated_models.update(
                 {
@@ -239,76 +342,63 @@ def candidates(
                 }
             )
 
-    newly_calibrated_models = None
-    if newly_calibrated_models_yamls is not None:
-        newly_calibrated_models = {}
-        for newly_calibrated_models_yaml in newly_calibrated_models_yamls:
-            newly_calibrated_models.update(
-                {
-                    model.get_hash(): model
-                    for model in models_from_yaml_list(
-                        newly_calibrated_models_yaml
-                    )
-                }
-            )
-
-    ui.candidates(
-        problem=problem,
+    # Finalize iteration results
+    iteration_results = ui.end_iteration(
         candidate_space=candidate_space,
-        previous_predecessor_model=previous_predecessor_model,
         calibrated_models=calibrated_models,
-        newly_calibrated_models=newly_calibrated_models,
-        limit=limit,
-        limit_sent=limit_sent,
-        excluded_models=excluded_models,
-        excluded_model_hashes=excluded_model_hashes,
     )
 
-    # Save state
+    # Save iteration results
+    ## State
     write_state(
         state=get_state(
             problem=problem,
-            candidate_space=candidate_space,
+            candidate_space=iteration_results[CANDIDATE_SPACE],
         ),
-        filename=state_filename,
+        filename=state_dill,
     )
-
-    # Save candidates
+    ## Models
     models_to_yaml_list(
-        models=candidate_space.models,
-        output_yaml=output,
+        models=iteration_results[MODELS],
+        output_yaml=models_yaml,
         relative_paths=relative_paths,
     )
+    ## Metadata
+    metadata = {
+        TERMINATE: iteration_results[TERMINATE],
+    }
+    with open(metadata_yaml, "w") as f:
+        yaml.dump(metadata, f)
 
 
 @cli.command("model_to_petab")
 @click.option(
-    '--model',
-    '-m',
-    'models_yamls',
+    "--model",
+    "-m",
+    "models_yamls",
     multiple=True,
-    help='The PEtab Select model YAML file.',
+    help="The PEtab Select model YAML file.",
 )
 @click.option(
-    '--output',
-    '-o',
-    'output_path',
+    "--output",
+    "-o",
+    "output_path",
     type=str,
-    help='The directory where the PEtab files will be output.',
+    help="The directory where the PEtab files will be output.",
 )
 @click.option(
-    '--model_id',
-    '-i',
-    'model_id',
+    "--model_id",
+    "-i",
+    "model_id",
     type=str,
     default=None,
     help=(
-        '(Optional) The ID of the model to use, in case the YAML file '
-        'contains multiple models.'
+        "(Optional) The ID of the model to use, in case the YAML file "
+        "contains multiple models."
     ),
 )
 def model_to_petab(
-    models_yamls: List[str],
+    models_yamls: list[str],
     output_path: str,
     model_id: str = None,
 ) -> None:
@@ -330,17 +420,17 @@ def model_to_petab(
         for model in models:
             if model.model_id == model_id:
                 if model0 is not None:
-                    raise ValueError('There are multiple models with this ID.')
+                    raise ValueError("There are multiple models with this ID.")
                 model0 = model
                 # TODO could `break` here and remove the above `ValueError`
                 #      and the `model0` logic
     if model0 is None:
-        raise ValueError('Could not find a model with the specified model ID.')
+        raise ValueError("Could not find a model with the specified model ID.")
 
     if model_id is not None and model0.model_id != model_id:
         raise ValueError(
-            'The ID of the model from the YAML file does not match the '
-            'specified ID.'
+            "The ID of the model from the YAML file does not match the "
+            "specified ID."
         )
 
     result = ui.model_to_petab(model0, output_path)
@@ -349,22 +439,22 @@ def model_to_petab(
 
 @cli.command("models_to_petab")
 @click.option(
-    '--models',
-    '-m',
-    'models_yamls',
+    "--models",
+    "-m",
+    "models_yamls",
     type=str,
     multiple=True,
-    help='The PEtab Select model YAML file, containing a list of models.',
+    help="The PEtab Select model YAML file, containing a list of models.",
 )
 @click.option(
-    '--output',
-    '-o',
-    'output_path_prefix',
+    "--output",
+    "-o",
+    "output_path_prefix",
     type=str,
-    help='The directory where the PEtab files will be output. The PEtab files will be stored in a model-specific subdirectory.',
+    help="The directory where the PEtab files will be output. The PEtab files will be stored in a model-specific subdirectory.",
 )
 def models_to_petab(
-    models_yamls: List[str],
+    models_yamls: list[str],
     output_path_prefix: str,
 ) -> None:
     """Create a PEtab problem for each model in a PEtab Select model YAML file.
@@ -383,76 +473,76 @@ def models_to_petab(
         models.extend(models_from_yaml_list(models_yaml))
 
     model_ids = pd.Series([model.model_id for model in models])
-    duplicates = '\n'.join(set(model_ids[model_ids.duplicated()]))
+    duplicates = "\n".join(set(model_ids[model_ids.duplicated()]))
     if duplicates:
         raise ValueError(
-            'It appears that the provided PEtab Select model YAML file '
-            'contains multiple models with the same ID. The following '
-            f'duplicates were detected: {duplicates}'
+            "It appears that the provided PEtab Select model YAML file "
+            "contains multiple models with the same ID. The following "
+            f"duplicates were detected: {duplicates}"
         )
 
     results = ui.models_to_petab(
         models,
         output_path_prefix=output_path_prefix,
     )
-    result_string = '\n'.join(
+    result_string = "\n".join(
         [
-            '\t'.join([model.model_id, result[PETAB_YAML]])
-            for model, result in zip(models, results)
+            "\t".join([model.model_id, result[PETAB_YAML]])
+            for model, result in zip(models, results, strict=False)
         ]
     )
     print(result_string)
 
 
-@cli.command("best")
+@cli.command("get_best")
 @click.option(
-    '--problem',
-    '-p',
-    'problem_yaml',
+    "--problem",
+    "-p",
+    "problem_yaml",
     type=str,
-    help='The PEtab Select YAML problem file.',
+    help="The PEtab Select YAML problem file.",
 )
 @click.option(
-    '--models',
-    '-m',
-    'models_yamls',
+    "--models",
+    "-m",
+    "models_yamls",
     type=str,
     multiple=True,
-    help='A list of calibrated models.',
+    help="A list of calibrated models.",
 )
 @click.option(
-    '--output',
-    '-o',
-    'output',
+    "--output",
+    "-o",
+    "output",
     type=str,
-    help='The file where the best model will be stored.',
+    help="The file where the best model will be stored.",
 )
 @click.option(
-    '--state',
-    '-s',
-    'state_filename',
+    "--state",
+    "-s",
+    "state_filename",
     type=str,
     default=None,
-    help='The file that stores the state.',
+    help="The file that stores the state.",
 )
 @click.option(
-    '--criterion',
-    '-c',
-    'criterion',
+    "--criterion",
+    "-c",
+    "criterion",
     type=str,
     default=None,
-    help='The criterion by which models will be compared.',
+    help="The criterion by which models will be compared.",
 )
 @click.option(
-    '--relative-paths/--absolute-paths',
-    'relative_paths',
+    "--relative-paths/--absolute-paths",
+    "relative_paths",
     type=bool,
     default=False,
-    help='Whether to output paths relative to the output file.',
+    help="Whether to output paths relative to the output file.",
 )
-def best(
+def get_best(
     problem_yaml: str,
-    models_yamls: List[str],
+    models_yamls: list[str],
     output: str,
     state_filename: str = None,
     criterion: str = None,
@@ -469,24 +559,11 @@ def best(
 
     problem = Problem.from_yaml(problem_yaml)
 
-    if state_filename is not None:
-        state = read_state(state_filename)
-        check_state_compatibility(
-            state=state,
-            problem=problem,
-            candidate_space=None,
-        )
-        set_state(
-            state=state,
-            problem=problem,
-            candidate_space=None,
-        )
-
     models = []
     for models_yaml in models_yamls:
         models.extend(models_from_yaml_list(models_yaml))
 
-    best_model = ui.best(
+    best_model = ui.get_best(
         problem=problem,
         models=models,
         criterion=criterion,
@@ -494,7 +571,8 @@ def best(
     best_model.to_yaml(output, paths_relative_to=paths_relative_to)
 
 
-cli.add_command(candidates)
+cli.add_command(start_iteration)
+cli.add_command(end_iteration)
 cli.add_command(model_to_petab)
 cli.add_command(models_to_petab)
-cli.add_command(best)
+cli.add_command(get_best)
diff --git a/petab_select/constants.py b/petab_select/constants.py
index ac0b2a73..b56e8a41 100644
--- a/petab_select/constants.py
+++ b/petab_select/constants.py
@@ -1,8 +1,10 @@
 """Constants for the PEtab Select package."""
+
+import string
 import sys
 from enum import Enum
 from pathlib import Path
-from typing import Dict, List, Literal, Union
+from typing import Literal
 
 # Zero-indexed column/row indices
 MODEL_ID_COLUMN = 0
@@ -12,42 +14,48 @@
 PARAMETER_DEFINITIONS_START = 2
 HEADER_ROW = 0
 
-PARAMETER_VALUE_DELIMITER = ';'
-CODE_DELIMITER = '-'
-ESTIMATE = 'estimate'
+PARAMETER_VALUE_DELIMITER = ";"
+CODE_DELIMITER = "-"
+ESTIMATE = "estimate"
 PETAB_ESTIMATE_FALSE = 0
 PETAB_ESTIMATE_TRUE = 1
 
 # TYPING_PATH = Union[str, Path]
-TYPE_PATH = Union[str, Path]
+TYPE_PATH = str | Path
 
 # Model space file columns
 # TODO ensure none of these occur twice in the column header (this would
 #      suggest that a parameter has a conflicting name)
 # MODEL_ID = 'modelId'  # TODO already defined, reorganize constants
 # YAML = 'YAML'  # FIXME
-MODEL_ID = 'model_id'
-MODEL_SUBSPACE_ID = 'model_subspace_id'
-MODEL_SUBSPACE_INDICES = 'model_subspace_indices'
-MODEL_CODE = 'model_code'
-MODEL_HASH = 'model_hash'
-MODEL_HASHES = 'model_hashes'
+MODEL_ID = "model_id"
+MODEL_SUBSPACE_ID = "model_subspace_id"
+MODEL_SUBSPACE_INDICES = "model_subspace_indices"
+MODEL_CODE = "model_code"
+MODEL_HASH = "model_hash"
+MODEL_HASHES = "model_hashes"
+MODEL_HASH_DELIMITER = "-"
+MODEL_SUBSPACE_INDICES_HASH_DELIMITER = "."
+MODEL_SUBSPACE_INDICES_HASH_MAP = (
+    # [0-9]+[A-Z]+[a-z]
+    string.digits + string.ascii_uppercase + string.ascii_lowercase
+)
+PETAB_HASH_DIGEST_SIZE = None
 # If `predecessor_model_hash` is defined for a model, it is the ID of the model that the
 # current model was/is to be compared to. This is part of the result and is
 # only (optionally) set by the PEtab calibration tool. It is not defined by the
 # PEtab Select model selection problem (but may be subsequently stored in the
 # PEtab Select model report format.
-PREDECESSOR_MODEL_HASH = 'predecessor_model_hash'
-PETAB_PROBLEM = 'petab_problem'
-PETAB_YAML = 'petab_yaml'
-SBML = 'sbml'
-HASH = 'hash'
+PREDECESSOR_MODEL_HASH = "predecessor_model_hash"
+PETAB_PROBLEM = "petab_problem"
+PETAB_YAML = "petab_yaml"
+HASH = "hash"
 
 # MODEL_SPACE_FILE_NON_PARAMETER_COLUMNS = [MODEL_ID, PETAB_YAML]
 MODEL_SPACE_FILE_NON_PARAMETER_COLUMNS = [MODEL_SUBSPACE_ID, PETAB_YAML]
 
 # COMPARED_MODEL_ID = 'compared_'+MODEL_ID
-YAML_FILENAME = 'yaml'
+YAML_FILENAME = "yaml"
 
 # DISTANCES = {
 #    FORWARD: {
@@ -64,34 +72,39 @@
 #    },
 # }
 
-CRITERIA = 'criteria'
+CRITERIA = "criteria"
 
-PARAMETERS = 'parameters'
+PARAMETERS = "parameters"
 # PARAMETER_ESTIMATE = 'parameter_estimate'
-ESTIMATED_PARAMETERS = 'estimated_parameters'
-
-CRITERION = 'criterion'
-METHOD = 'method'
-VERSION = 'version'
-MODEL_SPACE_FILES = 'model_space_files'
-
-CANDIDATE_SPACE_ARGUMENTS = 'candidate_space_arguments'
-METHOD_SCHEME = 'method_scheme'
-PREVIOUS_METHODS = 'previous_methods'
-NEXT_METHOD = 'next_method'
-PREDECESSOR_MODEL = 'predecessor_model'
-
-MODEL = 'model'
-MODELS = 'models'
+ESTIMATED_PARAMETERS = "estimated_parameters"
+
+# Problem keys
+CRITERION = "criterion"
+METHOD = "method"
+VERSION = "version"
+MODEL_SPACE_FILES = "model_space_files"
+PROBLEM_ID = "problem_id"
+
+CANDIDATE_SPACE = "candidate_space"
+CANDIDATE_SPACE_ARGUMENTS = "candidate_space_arguments"
+METHOD_SCHEME = "method_scheme"
+PREVIOUS_METHODS = "previous_methods"
+NEXT_METHOD = "next_method"
+PREDECESSOR_MODEL = "predecessor_model"
+
+MODEL = "model"
+MODELS = "models"
+UNCALIBRATED_MODELS = "uncalibrated_models"
+TERMINATE = "terminate"
 
 # Parameters can be fixed to a value, or estimated if indicated with the string
 # `ESTIMATE`.
-TYPE_PARAMETER = Union[float, int, Literal[ESTIMATE]]
-TYPE_PARAMETER_OPTIONS = List[TYPE_PARAMETER]
+TYPE_PARAMETER = float | int | Literal[ESTIMATE]
+TYPE_PARAMETER_OPTIONS = list[TYPE_PARAMETER]
 # Parameter ID -> parameter value mapping.
-TYPE_PARAMETER_DICT = Dict[str, TYPE_PARAMETER]
+TYPE_PARAMETER_DICT = dict[str, TYPE_PARAMETER]
 # Parameter ID -> multiple possible parameter values.
-TYPE_PARAMETER_OPTIONS_DICT = Dict[str, TYPE_PARAMETER_OPTIONS]
+TYPE_PARAMETER_OPTIONS_DICT = dict[str, TYPE_PARAMETER_OPTIONS]
 
 TYPE_CRITERION = float
 
@@ -100,34 +113,36 @@ class Method(str, Enum):
     """String literals for model selection methods."""
 
     #: The backward stepwise method.
-    BACKWARD = 'backward'
+    BACKWARD = "backward"
     #: The brute-force method.
-    BRUTE_FORCE = 'brute_force'
+    BRUTE_FORCE = "brute_force"
     #: The FAMoS method.
-    FAMOS = 'famos'
+    FAMOS = "famos"
     #: The forward stepwise method.
-    FORWARD = 'forward'
+    FORWARD = "forward"
     #: The lateral, or swap, method.
-    LATERAL = 'lateral'
+    LATERAL = "lateral"
     #: The jump-to-most-distant-model method.
-    MOST_DISTANT = 'most_distant'
+    MOST_DISTANT = "most_distant"
 
 
 class Criterion(str, Enum):
     """String literals for model selection criteria."""
 
     #: The Akaike information criterion.
-    AIC = 'AIC'
+    AIC = "AIC"
     #: The corrected Akaike information criterion.
-    AICC = 'AICc'
+    AICC = "AICc"
     #: The Bayesian information criterion.
-    BIC = 'BIC'
+    BIC = "BIC"
     #: The likelihood.
-    LH = 'LH'
+    LH = "LH"
     #: The log-likelihood.
-    LLH = 'LLH'
+    LLH = "LLH"
     #: The negative log-likelihood.
-    NLLH = 'NLLH'
+    NLLH = "NLLH"
+    #: The sum of squared residuals.
+    SSR = "SSR"
 
 
 #: Methods that move through model space by taking steps away from some model.
@@ -144,7 +159,7 @@ class Criterion(str, Enum):
 ]
 
 #: Virtual initial models can be used to initialize some initial model methods.
-VIRTUAL_INITIAL_MODEL = 'virtual_initial_model'
+VIRTUAL_INITIAL_MODEL = "virtual_initial_model"
 #: Methods that are compatible with a virtual initial model.
 VIRTUAL_INITIAL_MODEL_METHODS = [
     Method.BACKWARD,
@@ -155,6 +170,6 @@ class Criterion(str, Enum):
 __all__ = [
     x
     for x in dir(sys.modules[__name__])
-    if not x.startswith('_')
-    and x not in ('sys', "Enum", "Path", "Dict", "List", "Literal", "Union")
+    if not x.startswith("_")
+    and x not in ("sys", "Enum", "Path", "Dict", "List", "Literal", "Union")
 ]
diff --git a/petab_select/criteria.py b/petab_select/criteria.py
index 1f245a28..86125009 100644
--- a/petab_select/criteria.py
+++ b/petab_select/criteria.py
@@ -1,18 +1,18 @@
 """Implementations of model selection criteria."""
 
 import numpy as np
-import petab
-from petab.C import OBJECTIVE_PRIOR_PARAMETERS, OBJECTIVE_PRIOR_TYPE
+import petab.v1 as petab
+from petab.v1.C import OBJECTIVE_PRIOR_PARAMETERS, OBJECTIVE_PRIOR_TYPE
 
 import petab_select
 
 from .constants import PETAB_PROBLEM, Criterion  # LH,; LLH,; NLLH,
 
 __all__ = [
-    'calculate_aic',
-    'calculate_aicc',
-    'calculate_bic',
-    'CriterionComputer',
+    "calculate_aic",
+    "calculate_aicc",
+    "calculate_bic",
+    "CriterionComputer",
 ]
 
 
@@ -22,7 +22,7 @@ class CriterionComputer:
 
     def __init__(
         self,
-        model: 'petab_select.model.Model',
+        model: "petab_select.model.Model",
     ):
         self.model = model
         self._petab_problem = None
@@ -54,7 +54,7 @@ def __call__(self, criterion: Criterion) -> float:
         Returns:
             The criterion value.
         """
-        return getattr(self, 'get_' + criterion.value.lower())()
+        return getattr(self, "get_" + criterion.value.lower())()
 
     def get_aic(self) -> float:
         """Get the Akaike information criterion."""
@@ -109,7 +109,7 @@ def get_lh(self) -> float:
             return np.exp(-1 * nllh)
 
         raise ValueError(
-            'Please supply the likelihood (LH) or a compatible transformation. Compatible transformations: log(LH), -log(LH).'
+            "Please supply the likelihood (LH) or a compatible transformation. Compatible transformations: log(LH), -log(LH)."
         )
 
     def get_n_estimated(self) -> int:
@@ -135,7 +135,7 @@ def get_n_priors(self) -> int:
             OBJECTIVE_PRIOR_TYPE in df and OBJECTIVE_PRIOR_PARAMETERS in df
         ):
             raise NotImplementedError(
-                'Currently expect that prior types are specified with prior parameters (no default values). Please provide an example for implementation.'
+                "Currently expect that prior types are specified with prior parameters (no default values). Please provide an example for implementation."
             )
 
         # Expect that the number of non-empty values in both objective prior columns
@@ -145,7 +145,7 @@ def get_n_priors(self) -> int:
             == df[OBJECTIVE_PRIOR_PARAMETERS].notna().sum()
         ):
             raise NotImplementedError(
-                'Some objective prior values are missing.'
+                "Some objective prior values are missing."
             )
 
         number_of_priors = df[OBJECTIVE_PRIOR_TYPE].notna().sum()
diff --git a/petab_select/handlers.py b/petab_select/handlers.py
index 573acf7d..856e0d46 100644
--- a/petab_select/handlers.py
+++ b/petab_select/handlers.py
@@ -1,7 +1,7 @@
-from typing import Callable, Union
+from collections.abc import Callable
 
 # `float` for `np.inf`
-TYPE_LIMIT = Union[float, int]
+TYPE_LIMIT = float | int
 
 
 # TODO exclusions handler
diff --git a/petab_select/misc.py b/petab_select/misc.py
index bb9cbcb9..b2e493fb 100644
--- a/petab_select/misc.py
+++ b/petab_select/misc.py
@@ -1,7 +1,7 @@
 import hashlib
 
 # import json
-from typing import Any, List, Union
+from typing import Any
 
 from .constants import (  # TYPE_PARAMETER_OPTIONS_DICT,
     ESTIMATE,
@@ -10,42 +10,48 @@
 )
 
 __all__ = [
-    'parameter_string_to_value',
+    "parameter_string_to_value",
 ]
 
 
-def hashify(x: Any) -> str:
+def hashify(x: Any, **kwargs) -> str:
     """Generate a hash.
 
+    Currently uses the :func:`hashlib.blake2b` method. `**kwargs` are forwarded
+    to this method.
+
     Args:
         x:
             The object to hash. `x` will be converted to a string (`str(x)`), then
             hashed.
 
     Returns:
-        The hash.
+        The hash, as a hexadecimal string.
     """
     # return int(hashlib.sha256(str(x).encode('utf-8')).hexdigest(), 16)
-    return hashlib.blake2b(str(x).encode('utf-8')).hexdigest()
+    return hashlib.blake2b(
+        str(x).encode("utf-8"),
+        **kwargs,
+    ).hexdigest()
 
 
-def hash_parameter_dict(dict_: TYPE_PARAMETER_DICT):
+def hash_parameter_dict(dict_: TYPE_PARAMETER_DICT, **kwargs):
     """Hash a dictionary of parameter values."""
     value = tuple((k, dict_[k]) for k in sorted(dict_))
-    return hashify(value)
+    return hashify(value, **kwargs)
 
 
-def hash_parameter_options(list_: TYPE_PARAMETER_OPTIONS):
+def hash_parameter_options(list_: TYPE_PARAMETER_OPTIONS, **kwargs):
     """Hash parameter options."""
-    return hashify(list(list_))
+    return hashify(list(list_), **kwargs)
 
 
-def hash_str(str_: str):
-    return hashify(str_)
+def hash_str(str_: str, **kwargs):
+    return hashify(str_, **kwargs)
 
 
-def hash_list(list_: List):
-    return hashify(list(list_))
+def hash_list(list_: list, **kwargs):
+    return hashify(list(list_), **kwargs)
 
 
 def snake_case_to_camel_case(string: str) -> str:
@@ -58,8 +64,8 @@ def snake_case_to_camel_case(string: str) -> str:
     Returns:
         The string, in camel case.
     """
-    string_pieces = string.split('_')
-    string_camel = ''
+    string_pieces = string.split("_")
+    string_camel = ""
     for string_piece in string_pieces:
         string_camel += string_piece[0].upper() + string_piece[1:]
     return string_camel
@@ -68,7 +74,7 @@ def snake_case_to_camel_case(string: str) -> str:
 def parameter_string_to_value(
     parameter_string: str,
     passthrough_estimate: bool = False,
-) -> Union[float, int, str]:
+) -> float | int | str:
     """Cast a parameter value from string to numeric.
 
     Args:
@@ -84,7 +90,7 @@ def parameter_string_to_value(
     if parameter_string == ESTIMATE:
         if passthrough_estimate:
             return parameter_string
-        raise ValueError('Please handle estimated parameters differently.')
+        raise ValueError("Please handle estimated parameters differently.")
     float_value = float(parameter_string)
     int_value = int(float_value)
 
diff --git a/petab_select/model.py b/petab_select/model.py
index 55ddf56f..6c7602f4 100644
--- a/petab_select/model.py
+++ b/petab_select/model.py
@@ -1,21 +1,27 @@
 """The `Model` class."""
+
+from __future__ import annotations
+
 import warnings
 from os.path import relpath
 from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple, Union
+from typing import TYPE_CHECKING, Any
 
-import petab
+import petab.v1 as petab
 import yaml
 from more_itertools import one
-from petab.C import ESTIMATE, NOMINAL_VALUE
+from petab.v1.C import ESTIMATE, NOMINAL_VALUE
 
 from .constants import (
     CRITERIA,
     ESTIMATED_PARAMETERS,
     MODEL_HASH,
+    MODEL_HASH_DELIMITER,
     MODEL_ID,
     MODEL_SUBSPACE_ID,
     MODEL_SUBSPACE_INDICES,
+    MODEL_SUBSPACE_INDICES_HASH_DELIMITER,
+    MODEL_SUBSPACE_INDICES_HASH_MAP,
     PARAMETERS,
     PETAB_ESTIMATE_TRUE,
     PETAB_PROBLEM,
@@ -29,18 +35,19 @@
 )
 from .criteria import CriterionComputer
 from .misc import (
-    hash_list,
-    hash_parameter_dict,
-    hash_str,
     parameter_string_to_value,
 )
 from .petab import PetabMixin
 
+if TYPE_CHECKING:
+    from .problem import Problem
+
 __all__ = [
-    'Model',
-    'default_compare',
-    'models_from_yaml_list',
-    'models_to_yaml_list',
+    "Model",
+    "default_compare",
+    "models_from_yaml_list",
+    "models_to_yaml_list",
+    "ModelHash",
 ]
 
 
@@ -58,14 +65,6 @@ class Model(PetabMixin):
             Functions to convert attributes from :class:`Model` to YAML.
         criteria:
             The criteria values of the calibrated model (e.g. AIC).
-        hash_attributes:
-            This attribute is currently not used.
-            Attributes that will be used to calculate the hash of the
-            :class:`Model` instance. NB: this hash is used during pairwise comparison
-            to determine whether any two :class:`Model` instances are unique. The
-            model instances are compared by their parameter estimation
-            problems, as opposed to parameter estimation results, which may
-            differ due to e.g. floating-point arithmetic.
         model_id:
             The model ID.
         petab_yaml:
@@ -130,42 +129,20 @@ class Model(PetabMixin):
             for criterion_id, criterion_value in x.items()
         },
     }
-    hash_attributes = {
-        # MODEL_ID: lambda x: hash(x),  # possible circular dependency on hash
-        # MODEL_SUBSPACE_ID: lambda x: hash(x),
-        # MODEL_SUBSPACE_INDICES: hash_list,
-        # TODO replace `YAML` with `PETAB_PROBLEM_HASH`, as YAML could refer to
-        #      different problems if used on different filesystems or sometimes
-        #      absolute and other times relative. Better to check whether the
-        #      PEtab problem itself is unique.
-        # TODO replace `PARAMETERS` with `PARAMETERS_ALL`, which should be al
-        #      parameters in the PEtab problem. This avoids treating the PEtab problem
-        #      differently to the model (in a subspace with the PEtab problem) that has
-        #      all nominal values defined in the subspace.
-        # TODO add `estimated_parameters`? Needs to be clarified whether this hash
-        #      should be unique amongst only not-yet-calibrated models, or may also
-        #      return the same value between differently parameterized models that ended
-        #      up being calibrated to be the same... probably should be the former.
-        #      Currently, the hash is stored, hence will "persist" after calibration
-        #      if the same `Model` instance is used.
-        # PETAB_YAML: lambda x: hash(x),
-        PETAB_YAML: hash_str,
-        PARAMETERS: hash_parameter_dict,
-    }
 
     def __init__(
         self,
         petab_yaml: TYPE_PATH,
         model_subspace_id: str = None,
         model_id: str = None,
-        model_subspace_indices: List[int] = None,
+        model_subspace_indices: list[int] = None,
         predecessor_model_hash: str = None,
-        parameters: Dict[str, Union[int, float]] = None,
-        estimated_parameters: Dict[str, Union[int, float]] = None,
-        criteria: Dict[str, float] = None,
+        parameters: dict[str, int | float] = None,
+        estimated_parameters: dict[str, int | float] = None,
+        criteria: dict[str, float] = None,
         # Optionally provided to reduce repeated parsing of `petab_yaml`.
-        petab_problem: Optional[petab.Problem] = None,
-        model_hash: Optional[Any] = None,
+        petab_problem: petab.Problem | None = None,
+        model_hash: Any | None = None,
     ):
         self.model_id = model_id
         self.model_subspace_id = model_subspace_id
@@ -174,9 +151,12 @@ def __init__(
         self.parameters = parameters
         self.estimated_parameters = estimated_parameters
         self.criteria = criteria
-        self.model_hash = model_hash
 
         self.predecessor_model_hash = predecessor_model_hash
+        if self.predecessor_model_hash is not None:
+            self.predecessor_model_hash = ModelHash.from_hash(
+                self.predecessor_model_hash
+            )
 
         if self.parameters is None:
             self.parameters = {}
@@ -187,6 +167,15 @@ def __init__(
 
         super().__init__(petab_yaml=petab_yaml, petab_problem=petab_problem)
 
+        self.model_hash = None
+        self.get_hash()
+        if model_hash is not None:
+            model_hash = ModelHash.from_hash(model_hash)
+            if self.model_hash != model_hash:
+                raise ValueError(
+                    "The supplied model hash does not match the computed "
+                    "model hash."
+                )
         if self.model_id is None:
             self.model_id = self.get_hash()
 
@@ -203,14 +192,15 @@ def set_criterion(self, criterion: Criterion, value: float) -> None:
         """
         if criterion in self.criteria:
             warnings.warn(
-                'Overwriting saved criterion value. '
-                f'Criterion: {criterion}. Value: {self.get_criterion(criterion)}.'
+                "Overwriting saved criterion value. "
+                f"Criterion: {criterion}. Value: {self.get_criterion(criterion)}.",
+                stacklevel=2,
             )
             # FIXME debug why value is overwritten during test case 0002.
             if False:
                 print(
-                    'Overwriting saved criterion value. '
-                    f'Criterion: {criterion}. Value: {self.get_criterion(criterion)}.'
+                    "Overwriting saved criterion value. "
+                    f"Criterion: {criterion}. Value: {self.get_criterion(criterion)}."
                 )
                 breakpoint()
         self.criteria[criterion] = value
@@ -230,7 +220,7 @@ def get_criterion(
         criterion: Criterion,
         compute: bool = True,
         raise_on_failure: bool = True,
-    ) -> Union[TYPE_CRITERION, None]:
+    ) -> TYPE_CRITERION | None:
         """Get a criterion value for the model.
 
         Args:
@@ -284,15 +274,17 @@ def compute_criterion(
             criterion_value = self.criterion_computer(criterion)
             self.set_criterion(criterion, criterion_value)
             result = criterion_value
-        except ValueError:
+        except ValueError as err:
             if raise_on_failure:
-                raise
+                raise ValueError(
+                    f"Insufficient information to compute criterion `{criterion}`."
+                ) from err
             result = None
         return result
 
     def set_estimated_parameters(
         self,
-        estimated_parameters: Dict[str, float],
+        estimated_parameters: dict[str, float],
         scaled: bool = False,
     ) -> None:
         """Set the estimated parameters.
@@ -313,10 +305,10 @@ def set_estimated_parameters(
 
     @staticmethod
     def from_dict(
-        model_dict: Dict[str, Any],
+        model_dict: dict[str, Any],
         base_path: TYPE_PATH = None,
         petab_problem: petab.Problem = None,
-    ) -> 'Model':
+    ) -> Model:
         """Generate a model from a dictionary of attributes.
 
         Args:
@@ -341,7 +333,9 @@ def from_dict(
         unknown_attributes = set(model_dict).difference(Model.converters_load)
         if unknown_attributes:
             warnings.warn(
-                'Ignoring unknown attributes: ' + ', '.join(unknown_attributes)
+                "Ignoring unknown attributes: "
+                + ", ".join(unknown_attributes),
+                stacklevel=2,
             )
 
         if base_path is not None:
@@ -356,7 +350,7 @@ def from_dict(
         return Model(**model_dict)
 
     @staticmethod
-    def from_yaml(model_yaml: TYPE_PATH) -> 'Model':
+    def from_yaml(model_yaml: TYPE_PATH) -> Model:
         """Generate a model from a PEtab Select model YAML file.
 
         Args:
@@ -376,10 +370,10 @@ def from_yaml(model_yaml: TYPE_PATH) -> 'Model':
                 if len(model_dict) <= 1:
                     raise
                 raise ValueError(
-                    'The provided YAML file contains a list with greater than '
-                    'one element. Use the `models_from_yaml_list` method or '
-                    'provide a PEtab Select model YAML file with only one '
-                    'model specified.'
+                    "The provided YAML file contains a list with greater than "
+                    "one element. Use the `models_from_yaml_list` method or "
+                    "provide a PEtab Select model YAML file with only one "
+                    "model specified."
                 )
 
         return Model.from_dict(model_dict, base_path=Path(model_yaml).parent)
@@ -387,8 +381,8 @@ def from_yaml(model_yaml: TYPE_PATH) -> 'Model':
     def to_dict(
         self,
         resolve_paths: bool = True,
-        paths_relative_to: Union[str, Path] = None,
-    ) -> Dict[str, Any]:
+        paths_relative_to: str | Path = None,
+    ) -> dict[str, Any]:
         """Generate a dictionary from the attributes of a :class:`Model` instance.
 
         Args:
@@ -436,15 +430,15 @@ def to_yaml(self, petab_yaml: TYPE_PATH, *args, **kwargs) -> None:
         # FIXME change `getattr(self, PETAB_YAML)` to be relative to
         # destination?
         # kind of fixed, as the path will be resolved in `to_dict`.
-        with open(petab_yaml, 'w') as f:
+        with open(petab_yaml, "w") as f:
             yaml.dump(self.to_dict(*args, **kwargs), f)
         # yaml.dump(self.to_dict(), str(petab_yaml))
 
     def to_petab(
         self,
         output_path: TYPE_PATH = None,
-        set_estimated_parameters: Optional[bool] = None,
-    ) -> Dict[str, Union[petab.Problem, TYPE_PATH]]:
+        set_estimated_parameters: bool | None = None,
+    ) -> dict[str, petab.Problem | TYPE_PATH]:
         """Generate a PEtab problem.
 
         Args:
@@ -490,9 +484,9 @@ def to_petab(
             # Else the parameter is to be fixed.
             else:
                 petab_problem.parameter_df.loc[parameter_id, ESTIMATE] = 0
-                petab_problem.parameter_df.loc[
-                    parameter_id, NOMINAL_VALUE
-                ] = parameter_string_to_value(parameter_value)
+                petab_problem.parameter_df.loc[parameter_id, NOMINAL_VALUE] = (
+                    parameter_string_to_value(parameter_value)
+                )
                 # parameter_value
 
         petab_yaml = None
@@ -508,32 +502,26 @@ def to_petab(
             PETAB_YAML: petab_yaml,
         }
 
-    def get_hash(self) -> int:
+    def get_hash(self) -> str:
         """Get the model hash.
 
-        Currently designed to only use pre-calibration information, such that if a model
-        is calibrated twice and the two calibrated models differ in their parameter
-        estimates, then they will still have the same hash.
+        See the documentation for :class:`ModelHash` for more information.
 
-        This is not implemented as ``__hash__`` because Python automatically truncates
-        values in a system-dependent manner, which reduces interoperability
+        This is not implemented as ``__hash__`` because Python automatically
+        truncates values in a system-dependent manner, which reduces
+        interoperability
         ( https://docs.python.org/3/reference/datamodel.html#object.__hash__ ).
 
         Returns:
             The hash.
         """
         if self.model_hash is None:
-            self.model_hash = hash_list(
-                [
-                    method(getattr(self, attribute))
-                    for attribute, method in Model.hash_attributes.items()
-                ]
-            )
+            self.model_hash = ModelHash.from_model(model=self)
         return self.model_hash
 
     def __hash__(self) -> None:
         """Use `Model.get_hash` instead."""
-        raise NotImplementedError('Use `Model.get_hash() instead.`')
+        raise NotImplementedError("Use `Model.get_hash() instead.`")
 
     def __str__(self):
         """Get a print-ready string representation of the model.
@@ -541,17 +529,17 @@ def __str__(self):
         Returns:
             The print-ready string representation, in TSV format.
         """
-        parameter_ids = '\t'.join(self.parameters.keys())
-        parameter_values = '\t'.join(str(v) for v in self.parameters.values())
-        header = '\t'.join([MODEL_ID, PETAB_YAML, parameter_ids])
-        data = '\t'.join(
+        parameter_ids = "\t".join(self.parameters.keys())
+        parameter_values = "\t".join(str(v) for v in self.parameters.values())
+        header = "\t".join([MODEL_ID, PETAB_YAML, parameter_ids])
+        data = "\t".join(
             [self.model_id, str(self.petab_yaml), parameter_values]
         )
         # header = f'{MODEL_ID}\t{PETAB_YAML}\t{parameter_ids}'
         # data = f'{self.model_id}\t{self.petab_yaml}\t{parameter_values}'
-        return f'{header}\n{data}'
+        return f"{header}\n{data}"
 
-    def get_mle(self) -> Dict[str, float]:
+    def get_mle(self) -> dict[str, float]:
         """Get the maximum likelihood estimate of the model."""
         """
         FIXME(dilpath)
@@ -587,7 +575,7 @@ def get_mle(self) -> Dict[str, float]:
         # TODO
         pass
 
-    def get_estimated_parameter_ids_all(self) -> List[str]:
+    def get_estimated_parameter_ids_all(self) -> list[str]:
         estimated_parameter_ids = []
 
         # Add all estimated parameters in the PEtab problem.
@@ -622,8 +610,8 @@ def get_estimated_parameter_ids_all(self) -> List[str]:
 
     def get_parameter_values(
         self,
-        parameter_ids: Optional[List[str]] = None,
-    ) -> List[TYPE_PARAMETER]:
+        parameter_ids: list[str] | None = None,
+    ) -> list[TYPE_PARAMETER]:
         """Get parameter values.
 
         Includes ``ESTIMATE`` for parameters that should be estimated.
@@ -679,14 +667,18 @@ def default_compare(
     """
     if not model1.has_criterion(criterion):
         warnings.warn(
-            f'Model "{model1.model_id}" does not provide a value for the criterion "{criterion}".'
+            f'Model "{model1.model_id}" does not provide a value for the '
+            f'criterion "{criterion}".',
+            stacklevel=2,
         )
         return False
     if model0 == VIRTUAL_INITIAL_MODEL or model0 is None:
         return True
     if criterion_threshold < 0:
         warnings.warn(
-            'The provided criterion threshold is negative. The absolute value will be used instead.'
+            "The provided criterion threshold is negative. "
+            "The absolute value will be used instead.",
+            stacklevel=2,
         )
         criterion_threshold = abs(criterion_threshold)
     if criterion in [
@@ -694,6 +686,7 @@ def default_compare(
         Criterion.AICC,
         Criterion.BIC,
         Criterion.NLLH,
+        Criterion.SSR,
     ]:
         return (
             model1.get_criterion(criterion)
@@ -708,14 +701,14 @@ def default_compare(
             > model0.get_criterion(criterion) + criterion_threshold
         )
     else:
-        raise NotImplementedError(f'Unknown criterion: {criterion}.')
+        raise NotImplementedError(f"Unknown criterion: {criterion}.")
 
 
 def models_from_yaml_list(
     model_list_yaml: TYPE_PATH,
     petab_problem: petab.Problem = None,
     allow_single_model: bool = True,
-) -> List[Model]:
+) -> list[Model]:
     """Generate a model from a PEtab Select list of model YAML file.
 
     Args:
@@ -734,7 +727,7 @@ def models_from_yaml_list(
     """
     with open(str(model_list_yaml)) as f:
         model_dict_list = yaml.safe_load(f)
-    if model_dict_list is None:
+    if not model_dict_list:
         return []
 
     if not isinstance(model_dict_list, list):
@@ -746,7 +739,7 @@ def models_from_yaml_list(
                     petab_problem=petab_problem,
                 )
             ]
-        raise ValueError('The YAML file does not contain a list of models.')
+        raise ValueError("The YAML file does not contain a list of models.")
 
     return [
         Model.from_dict(
@@ -759,10 +752,38 @@ def models_from_yaml_list(
 
 
 def models_to_yaml_list(
-    models: List[Model],
+    models: list[Model | str] | dict[ModelHash, Model | str],
     output_yaml: TYPE_PATH,
     relative_paths: bool = True,
-):
+) -> None:
+    """Generate a YAML listing of models.
+
+    Args:
+        models:
+            The models.
+        output_yaml:
+            The location where the YAML will be saved.
+        relative_paths:
+            Whether to rewrite the paths in each model (e.g. the path to the
+            model's PEtab problem) relative to the `output_yaml` location.
+    """
+    if isinstance(models, dict):
+        models = list(models.values())
+
+    skipped_indices = []
+    for index, model in enumerate(models):
+        if isinstance(model, Model):
+            continue
+        if model == VIRTUAL_INITIAL_MODEL:
+            continue
+        warnings.warn(f"Unexpected model, skipping: {model}.", stacklevel=2)
+        skipped_indices.append(index)
+    models = [
+        model
+        for index, model in enumerate(models)
+        if index not in skipped_indices
+    ]
+
     paths_relative_to = None
     if relative_paths:
         paths_relative_to = Path(output_yaml).parent
@@ -770,5 +791,287 @@ def models_to_yaml_list(
         model.to_dict(paths_relative_to=paths_relative_to) for model in models
     ]
     model_dicts = None if not model_dicts else model_dicts
-    with open(output_yaml, 'w') as f:
+    with open(output_yaml, "w") as f:
         yaml.dump(model_dicts, f)
+
+
+class ModelHash(str):
+    """A class to handle model hash functionality.
+
+    The model hash is designed to be human-readable and able to be converted
+    back into the corresponding model. Currently, if two models from two
+    different model subspaces are actually the same PEtab problem, they will
+    still have different model hashes.
+
+    Attributes:
+        model_subspace_id:
+            The ID of the model subspace of the model. Unique up to a single
+            PEtab Select problem model space.
+        model_subspace_indices_hash:
+            A hash of the location of the model in its model
+            subspace. Unique up to a single model subspace.
+    """
+
+    # FIXME petab problem--specific hashes that are cross-platform?
+    """
+    The model hash is designed to be: human-readable; able to be converted
+    back into the corresponding model, and unique up to the same PEtab
+    problem and parameters.
+
+    Consider two different models in different model subspaces, with
+    `ModelHash`s `model_hash0` and `model_hash1`, respectively. Assume that
+    these two models end up encoding the same PEtab problem (e.g. they set the
+    same parameters to be estimated).
+    The string representation will be different,
+    `str(model_hash0) != str(model_hash1)`, but their hashes will pass the
+    equality check: `model_hash0 == model_hash1` and
+    `hash(model_hash0) == hash(model_hash1)`.
+
+    This means that different models in different model subspaces that end up
+    being the same PEtab problem will have different human-readable hashes,
+    but if these models arise during model selection, then only one of them
+    will be calibrated.
+
+    The PEtab hash size is computed automatically as the smallest size that
+    ensures a collision probability of less than $2^{-64}$.
+    N.B.: this assumes only one model subspace, and only 2 options for each
+    parameter (e.g. `0` and `estimate`). You can manually set the size with
+    :const:`petab_select.constants.PETAB_HASH_DIGEST_SIZE`.
+
+    petab_hash:
+        A hash that is unique up to the same PEtab problem, which is
+        determined by: the PEtab problem YAML file location, nominal
+        parameter values, and parameters set to be estimated. This means
+        that different models may have the same `unique_petab_hash`,
+        because they are the same estimation problem.
+    """
+
+    def __init__(
+        self,
+        model_subspace_id: str,
+        model_subspace_indices_hash: str,
+        # petab_hash: str,
+    ):
+        self.model_subspace_id = model_subspace_id
+        self.model_subspace_indices_hash = model_subspace_indices_hash
+        # self.petab_hash = petab_hash
+
+    def __new__(
+        cls,
+        model_subspace_id: str,
+        model_subspace_indices_hash: str,
+        # petab_hash: str,
+    ):
+        hash_str = MODEL_HASH_DELIMITER.join(
+            [
+                model_subspace_id,
+                model_subspace_indices_hash,
+                # petab_hash,
+            ]
+        )
+        instance = super().__new__(cls, hash_str)
+        return instance
+
+    def __getnewargs_ex__(self):
+        return (
+            (),
+            {
+                "model_subspace_id": self.model_subspace_id,
+                "model_subspace_indices_hash": self.model_subspace_indices_hash,
+                # 'petab_hash': self.petab_hash,
+            },
+        )
+
+    def __copy__(self):
+        return ModelHash(
+            model_subspace_id=self.model_subspace_id,
+            model_subspace_indices_hash=self.model_subspace_indices_hash,
+            # petab_hash=self.petab_hash,
+        )
+
+    def __deepcopy__(self, memo):
+        return self.__copy__()
+
+    # @staticmethod
+    # def get_petab_hash(model: Model) -> str:
+    #     """Get a hash that is unique up to the same estimation problem.
+
+    #     See :attr:`petab_hash` for more information.
+
+    #     Args:
+    #         model:
+    #             The model.
+
+    #     Returns:
+    #         The unique PEtab hash.
+    #     """
+    #     digest_size = PETAB_HASH_DIGEST_SIZE
+    #     if digest_size is None:
+    #         petab_info_bits = len(model.model_subspace_indices)
+    #         # Ensure <2^{-64} probability of collision
+    #         petab_info_bits += 64
+    #         # Convert to bytes, round up.
+    #         digest_size = int(petab_info_bits / 8) + 1
+
+    #     petab_yaml = str(model.petab_yaml.resolve())
+    #     model_parameter_df = model.to_petab(set_estimated_parameters=False)[
+    #         PETAB_PROBLEM
+    #     ].parameter_df
+    #     nominal_parameter_hash = hash_parameter_dict(
+    #         model_parameter_df[NOMINAL_VALUE].to_dict()
+    #     )
+    #     estimate_parameter_hash = hash_parameter_dict(
+    #         model_parameter_df[ESTIMATE].to_dict()
+    #     )
+    #     return hash_str(
+    #         petab_yaml + estimate_parameter_hash + nominal_parameter_hash,
+    #         digest_size=digest_size,
+    #     )
+
+    @staticmethod
+    def from_hash(model_hash: str | ModelHash) -> ModelHash:
+        """Reconstruct a :class:`ModelHash` object.
+
+        Args:
+            model_hash:
+                The model hash.
+
+        Returns:
+            The :class:`ModelHash` object.
+        """
+        if isinstance(model_hash, ModelHash):
+            return model_hash
+
+        if model_hash == VIRTUAL_INITIAL_MODEL:
+            return ModelHash(
+                model_subspace_id=VIRTUAL_INITIAL_MODEL,
+                model_subspace_indices_hash="",
+                # petab_hash=VIRTUAL_INITIAL_MODEL,
+            )
+
+        (
+            model_subspace_id,
+            model_subspace_indices_hash,
+            # petab_hash,
+        ) = model_hash.split(MODEL_HASH_DELIMITER)
+        return ModelHash(
+            model_subspace_id=model_subspace_id,
+            model_subspace_indices_hash=model_subspace_indices_hash,
+            # petab_hash=petab_hash,
+        )
+
+    @staticmethod
+    def from_model(model: Model) -> ModelHash:
+        """Create a hash for a model.
+
+        Args:
+            model:
+                The model.
+
+        Returns:
+            The model hash.
+        """
+        model_subspace_id = ""
+        model_subspace_indices_hash = ""
+        if model.model_subspace_id is not None:
+            model_subspace_id = model.model_subspace_id
+            model_subspace_indices_hash = (
+                ModelHash.hash_model_subspace_indices(
+                    model.model_subspace_indices
+                )
+            )
+
+        return ModelHash(
+            model_subspace_id=model_subspace_id,
+            model_subspace_indices_hash=model_subspace_indices_hash,
+            # petab_hash=ModelHash.get_petab_hash(model=model),
+        )
+
+    @staticmethod
+    def hash_model_subspace_indices(model_subspace_indices: list[int]) -> str:
+        """Hash the location of a model in its subspace.
+
+        Args:
+            model_subspace_indices:
+                The location (indices) of the model in its subspace.
+
+        Returns:
+            The hash.
+        """
+        try:
+            return "".join(
+                MODEL_SUBSPACE_INDICES_HASH_MAP[index]
+                for index in model_subspace_indices
+            )
+        except KeyError:
+            return MODEL_SUBSPACE_INDICES_HASH_DELIMITER.join(
+                str(i) for i in model_subspace_indices
+            )
+
+    def unhash_model_subspace_indices(self) -> list[int]:
+        """Get the location of a model in its subspace.
+
+        Returns:
+            The location, as indices of the subspace.
+        """
+        if (
+            MODEL_SUBSPACE_INDICES_HASH_DELIMITER
+            in self.model_subspace_indices_hash
+        ):
+            return [
+                int(s)
+                for s in self.model_subspace_indices_hash.split(
+                    MODEL_SUBSPACE_INDICES_HASH_DELIMITER
+                )
+            ]
+        else:
+            return [
+                MODEL_SUBSPACE_INDICES_HASH_MAP.index(s)
+                for s in self.model_subspace_indices_hash
+            ]
+
+    def get_model(self, petab_select_problem: Problem) -> Model:
+        """Get the model that a hash corresponds to.
+
+        Args:
+            petab_select_problem:
+                The PEtab Select problem. The model will be found in its model
+                space.
+
+        Returns:
+            The model.
+        """
+        # if self.petab_hash == VIRTUAL_INITIAL_MODEL:
+        #     return self.petab_hash
+
+        return petab_select_problem.model_space.model_subspaces[
+            self.model_subspace_id
+        ].indices_to_model(
+            self.unhash_model_subspace_indices(
+                self.model_subspace_indices_hash
+            )
+        )
+
+    def __hash__(self) -> str:
+        """The PEtab hash.
+
+        N.B.: this is not the model hash! As the equality between two models
+        is determined by their PEtab hash only, this method only returns the
+        PEtab hash. However, the model hash is the full string with the
+        human-readable elements as well. :func:`ModelHash.from_hash` does not
+        accept the PEtab hash as input, rather the full string.
+        """
+        return hash(str(self))
+
+    def __eq__(self, other_hash: str | ModelHash) -> bool:
+        """Check whether two model hashes are equivalent.
+
+        Returns:
+            Whether the two hashes correspond to equivalent PEtab problems.
+        """
+        # petab_hash = other_hash
+        # # Check whether the PEtab hash needs to be extracted
+        # if MODEL_HASH_DELIMITER in other_hash:
+        #     petab_hash = ModelHash.from_hash(other_hash).petab_hash
+        # return self.petab_hash == petab_hash
+        return str(self) == str(other_hash)
diff --git a/petab_select/model_space.py b/petab_select/model_space.py
index 2b19c53c..f3237ae9 100644
--- a/petab_select/model_space.py
+++ b/petab_select/model_space.py
@@ -1,10 +1,12 @@
 """The `ModelSpace` class and related methods."""
+
 import itertools
 import logging
 import warnings
+from collections.abc import Iterable
 from pathlib import Path
 from tempfile import NamedTemporaryFile
-from typing import Any, Iterable, List, Optional, TextIO, Union, get_args
+from typing import Any, TextIO, get_args
 
 import numpy as np
 import pandas as pd
@@ -99,7 +101,7 @@ def line2row(
     delimiter: str = "\t",
     unpacked: bool = True,
     convert_parameters_to_float: bool = True,
-) -> List:
+) -> list:
     """Parse a line from a model space file.
 
     Args:
@@ -137,7 +139,7 @@ class ModelSpace:
 
     def __init__(
         self,
-        model_subspaces: List[ModelSubspace],
+        model_subspaces: list[ModelSubspace],
     ):
         self.model_subspaces = {
             model_subspace.model_subspace_id: model_subspace
@@ -146,7 +148,7 @@ def __init__(
 
     @staticmethod
     def from_files(
-        filenames: List[TYPE_PATH],
+        filenames: list[TYPE_PATH],
     ):
         """Create a model space from model space files.
 
@@ -163,7 +165,7 @@ def from_files(
         ]
         model_subspaces = []
         for model_space_df, model_space_filename in zip(
-            model_space_dfs, filenames
+            model_space_dfs, filenames, strict=False
         ):
             for model_subspace_id, definition in model_space_df.iterrows():
                 model_subspaces.append(
@@ -218,8 +220,9 @@ def search(
         """
         if candidate_space.limit.reached():
             warnings.warn(
-                'The candidate space has already reached its limit of accepted models.',
+                "The candidate space has already reached its limit of accepted models.",
                 RuntimeWarning,
+                stacklevel=2,
             )
             return candidate_space.models
 
@@ -229,7 +232,7 @@ def search_subspaces(only_one_subspace: bool = False):
             #      ID
             if only_one_subspace and len(self.model_subspaces) > 1:
                 logging.warning(
-                    f'There is more than one model subspace. This can lead to problems for candidate space {candidate_space}, especially if they have different PEtab YAML files.'
+                    f"There is more than one model subspace. This can lead to problems for candidate space {candidate_space}, especially if they have different PEtab YAML files."
                 )
             for model_subspace in self.model_subspaces.values():
                 model_subspace.search(
@@ -239,9 +242,9 @@ def search_subspaces(only_one_subspace: bool = False):
                     break
                 elif len(candidate_space.models) > limit:
                     raise ValueError(
-                        'An unknown error has occurred. Too many models were '
-                        f'generated. Requested limit: {limit}. Number of '
-                        f'generated models: {len(candidate_space.models)}.'
+                        "An unknown error has occurred. Too many models were "
+                        f"generated. Requested limit: {limit}. Number of "
+                        f"generated models: {len(candidate_space.models)}."
                     )
 
         search_subspaces()
@@ -285,24 +288,24 @@ def exclude_model_hashes(self, model_hashes: Iterable[str]):
 
     def reset_exclusions(
         self,
-        exclusions: Optional[Union[List[Any], None]] = None,
+        exclusions: list[Any] | None | None = None,
     ) -> None:
         """Reset the exclusions in the model subspaces."""
         for model_subspace in self.model_subspaces.values():
             model_subspace.reset_exclusions(exclusions)
 
 
-def get_model_space_df(df: Union[TYPE_PATH, pd.DataFrame]) -> pd.DataFrame:
+def get_model_space_df(df: TYPE_PATH | pd.DataFrame) -> pd.DataFrame:
     # model_space_df = pd.read_csv(filename, sep='\t', index_col=MODEL_SUBSPACE_ID)  # FIXME
     if isinstance(df, get_args(TYPE_PATH)):
-        df = pd.read_csv(df, sep='\t')
+        df = pd.read_csv(df, sep="\t")
     if df.index.name != MODEL_SUBSPACE_ID:
         df.set_index([MODEL_SUBSPACE_ID], inplace=True)
     return df
 
 
 def write_model_space_df(df: pd.DataFrame, filename: TYPE_PATH) -> None:
-    df.to_csv(filename, sep='\t', index=True)
+    df.to_csv(filename, sep="\t", index=True)
 
 
 # def get_model_space(
diff --git a/petab_select/model_subspace.py b/petab_select/model_subspace.py
index bfed9596..1f077996 100644
--- a/petab_select/model_subspace.py
+++ b/petab_select/model_subspace.py
@@ -1,8 +1,9 @@
 import math
 import warnings
+from collections.abc import Iterable, Iterator
 from itertools import product
 from pathlib import Path
-from typing import Any, Dict, Iterable, Iterator, List, Optional, Union
+from typing import Any
 
 import numpy as np
 import pandas as pd
@@ -27,7 +28,7 @@
 from .petab import PetabMixin
 
 __all__ = [
-    'ModelSubspace',
+    "ModelSubspace",
 ]
 
 
@@ -60,7 +61,7 @@ def __init__(
         model_subspace_id: str,
         petab_yaml: str,
         parameters: TYPE_PARAMETER_OPTIONS_DICT,
-        exclusions: Optional[Union[List[Any], None]] = None,
+        exclusions: list[Any] | None | None = None,
     ):
         self.model_subspace_id = model_subspace_id
         self.parameters = parameters
@@ -95,20 +96,21 @@ def check_compatibility_stepwise_method(
             != str(self.petab_yaml.resolve())
         ):
             warnings.warn(
-                'The supplied candidate space is initialized with a model '
-                'that has a different PEtab YAML to this model subspace. '
-                'This is currently not supported for stepwise methods '
-                '(e.g. forward or backward). '
-                f'This model subspace: `{self.model_subspace_id}`. '
-                'This model subspace PEtab YAML: '
-                f'`{self.petab_yaml}`. '
-                'The candidate space PEtab YAML: '
-                f'`{candidate_space.predecessor_model.petab_yaml}`. '
+                "The supplied candidate space is initialized with a model "
+                "that has a different PEtab YAML to this model subspace. "
+                "This is currently not supported for stepwise methods "
+                "(e.g. forward or backward). "
+                f"This model subspace: `{self.model_subspace_id}`. "
+                "This model subspace PEtab YAML: "
+                f"`{self.petab_yaml}`. "
+                "The candidate space PEtab YAML: "
+                f"`{candidate_space.predecessor_model.petab_yaml}`.",
+                stacklevel=2,
             )
             return False
         return True
 
-    def get_models(self, estimated_parameters: List[str]) -> Iterator[Model]:
+    def get_models(self, estimated_parameters: list[str]) -> Iterator[Model]:
         """Get models in the subspace by estimated parameters.
 
         All models that have the provided ``estimated_parameters`` are returned.
@@ -132,10 +134,10 @@ def get_models(self, estimated_parameters: List[str]) -> Iterator[Model]:
         """
         if set(estimated_parameters).difference(self.parameters):
             raise ValueError(
-                'Some parameter IDs were provided that are not in the model '
-                'subspace definition. NB: parameters that are only in the '
-                'PEtab parameters table should not be included here. '
-                f'IDs: {set(estimated_parameters).difference(self.parameters)}'
+                "Some parameter IDs were provided that are not in the model "
+                "subspace definition. NB: parameters that are only in the "
+                "PEtab parameters table should not be included here. "
+                f"IDs: {set(estimated_parameters).difference(self.parameters)}"
             )
         fixed_parameter_ids = [
             parameter_id
@@ -149,9 +151,9 @@ def get_models(self, estimated_parameters: List[str]) -> Iterator[Model]:
         ]
         if parameters_cannot_be_fixed_error:
             raise ValueError(
-                'Models with the following fixed parameters were requested; '
-                'however, there is no such model in this subspace: '
-                f'{parameters_cannot_be_fixed_error}.'
+                "Models with the following fixed parameters were requested; "
+                "however, there is no such model in this subspace: "
+                f"{parameters_cannot_be_fixed_error}."
             )
         # Identify possible values for each of the fixed parameters.
         fixed_options = [
@@ -168,6 +170,7 @@ def get_models(self, estimated_parameters: List[str]) -> Iterator[Model]:
                 zip(
                     fixed_parameter_ids,
                     fixed_parameter_values,
+                    strict=False,
                 )
             )
             parameters = {
@@ -227,6 +230,8 @@ def continue_searching(
         if not self.check_compatibility_stepwise_method(candidate_space):
             return
 
+        # TODO check inside `continue_searching` too? or move
+        # all limit handling to candidate space
         if candidate_space.limit.reached():
             return
 
@@ -244,15 +249,13 @@ def continue_searching(
                 # Should already be handled elsewhere (e.g.
                 # `self.check_compatibility_stepwise_method`).
                 raise NotImplementedError(
-                    f'The default parameter set for a candidate space with the virtual initial model and method {candidate_space.method} is not implemented. Please report if this is desired.'
+                    f"The default parameter set for a candidate space with the virtual initial model and method {candidate_space.method} is not implemented. Please report if this is desired."
                 )
         else:
             old_estimated_all = set()
             old_fixed_all = set()
             if isinstance(candidate_space.predecessor_model, Model):
-                old_estimated_all = (
-                    candidate_space.predecessor_model.get_estimated_parameter_ids_all()
-                )
+                old_estimated_all = candidate_space.predecessor_model.get_estimated_parameter_ids_all()
                 old_fixed_all = [
                     parameter_id
                     for parameter_id in self.parameters_all
@@ -286,7 +289,7 @@ def continue_searching(
             == new_must_estimate
         ):
             raise ValueError(
-                'Unexpected error (sets that should be equal are not).'
+                "Unexpected error (sets that should be equal are not)."
             )
         new_can_estimate_optional = (
             set(self.can_estimate)
@@ -482,7 +485,9 @@ def continue_searching(
         elif candidate_space.method == Method.BRUTE_FORCE:
             # TODO remove list?
             for parameterization in list(product(*self.parameters.values())):
-                parameters = dict(zip(self.parameters, parameterization))
+                parameters = dict(
+                    zip(self.parameters, parameterization, strict=False)
+                )
                 model = self.parameters_to_model(parameters)
                 # Skip models that are excluded.
                 if model is None:
@@ -582,15 +587,15 @@ def continue_searching(
 
         else:
             raise NotImplementedError(
-                'The requested method is not yet implemented in the model '
-                f'subspace interface: `{candidate_space.method}`.'
+                "The requested method is not yet implemented in the model "
+                f"subspace interface: `{candidate_space.method}`."
             )
 
     def send_model_to_candidate_space(
         self,
         model: Model,
         candidate_space: CandidateSpace,
-        exclude: Optional[bool] = False,
+        exclude: bool | None = False,
         # use_exclusions: Optional[bool] = True,
     ) -> bool:
         """Send a model to a candidate space for consideration.
@@ -674,7 +679,7 @@ def excluded(
     def reset_exclusions(
         self,
         # TODO change typing with `List[Any]` to some `List[TYPE_MODEL_HASH]`
-        exclusions: Optional[Union[List[Any], None]] = None,
+        exclusions: list[Any] | None | None = None,
     ):
         self.exclusions = set()
         if exclusions is not None:
@@ -682,8 +687,8 @@ def reset_exclusions(
 
     def reset(
         self,
-        exclusions: Optional[Union[List[Any], None]] = None,
-        limit: Optional[int] = None,
+        exclusions: list[Any] | None | None = None,
+        limit: int | None = None,
     ):
         self.reset_exclusions(exclusions=exclusions)
         if limit is not None:
@@ -692,9 +697,9 @@ def reset(
     @staticmethod
     def from_definition(
         model_subspace_id: str,
-        definition: Union[Dict[str, str], pd.Series],
+        definition: dict[str, str] | pd.Series,
         parent_path: TYPE_PATH = None,
-    ) -> 'ModelSubspace':
+    ) -> "ModelSubspace":
         """Create a :class:`ModelSubspace` from a definition.
 
         Args:
@@ -723,7 +728,7 @@ def from_definition(
             parameters=parameters,
         )
 
-    def indices_to_model(self, indices: List[int]) -> Union[Model, None]:
+    def indices_to_model(self, indices: list[int]) -> Model | None:
         """Get a model from the subspace, by indices of possible parameter values.
 
         Model exclusions are handled here.
@@ -751,7 +756,7 @@ def indices_to_model(self, indices: List[int]) -> Union[Model, None]:
 
     def indices_to_parameters(
         self,
-        indices: List[int],
+        indices: list[int],
     ) -> TYPE_PARAMETER_DICT:
         """Convert parameter indices to values.
 
@@ -764,7 +769,9 @@ def indices_to_parameters(
         """
         parameters = {
             parameter_id: self.parameters[parameter_id][index]
-            for parameter_id, index in zip(self.parameters, indices)
+            for parameter_id, index in zip(
+                self.parameters, indices, strict=False
+            )
         }
         return parameters
 
@@ -780,9 +787,9 @@ def parameters_to_indices(self, parameters: TYPE_PARAMETER_DICT):
         """
         if set(self.parameters).symmetric_difference(parameters):
             raise ValueError(
-                'Parameter IDs differ between the stored and provided '
-                'values: '
-                f'{set(self.parameters).symmetric_difference(parameters)}'
+                "Parameter IDs differ between the stored and provided "
+                "values: "
+                f"{set(self.parameters).symmetric_difference(parameters)}"
             )
         indices = []
         for parameter_id, parameter_values in self.parameters.items():
@@ -790,8 +797,8 @@ def parameters_to_indices(self, parameters: TYPE_PARAMETER_DICT):
                 index = parameter_values.index(parameters[parameter_id])
             except ValueError:
                 raise ValueError(
-                    f'The following value for the parameter {parameter_id} is '
-                    f'not in the model subspace: {parameters[parameter_id]}.'
+                    f"The following value for the parameter {parameter_id} is "
+                    f"not in the model subspace: {parameters[parameter_id]}."
                 )
             indices.append(index)
         return indices
@@ -799,7 +806,7 @@ def parameters_to_indices(self, parameters: TYPE_PARAMETER_DICT):
     def parameters_to_model(
         self,
         parameters: TYPE_PARAMETER_DICT,
-    ) -> Union[Model, None]:
+    ) -> Model | None:
         """Convert parameter values to a model.
 
         Args:
@@ -824,7 +831,7 @@ def parameters_all(self) -> TYPE_PARAMETER_DICT:
         return {**self.petab_parameters, **self.parameters}
 
     @property
-    def can_fix(self) -> List[str]:
+    def can_fix(self) -> list[str]:
         """Parameters that can be fixed, according to the subspace.
 
         Parameters that are fixed as part of the PEtab problem are not
@@ -841,7 +848,7 @@ def can_fix(self) -> List[str]:
         ]
 
     @property
-    def can_estimate(self) -> List[str]:
+    def can_estimate(self) -> list[str]:
         """Parameters that can be estimated, according to the subspace.
 
         Parameters that are estimated as part of the PEtab problem are not
@@ -854,7 +861,7 @@ def can_estimate(self) -> List[str]:
         ]
 
     @property
-    def can_estimate_all(self) -> List[str]:
+    def can_estimate_all(self) -> list[str]:
         """All parameters than can be estimated in this subspace."""
         return [
             parameter_id
@@ -863,7 +870,7 @@ def can_estimate_all(self) -> List[str]:
         ]
 
     @property
-    def must_fix(self) -> List[str]:
+    def must_fix(self) -> list[str]:
         """Subspace parameters that must be fixed.
 
         Parameters that are fixed as part of the PEtab problem are not
@@ -876,7 +883,7 @@ def must_fix(self) -> List[str]:
         ]
 
     @property
-    def must_fix_all(self) -> List[str]:
+    def must_fix_all(self) -> list[str]:
         """All parameters that must be fixed in this subspace."""
         return [
             parameter_id
@@ -885,7 +892,7 @@ def must_fix_all(self) -> List[str]:
         ]
 
     @property
-    def must_estimate(self) -> List[str]:
+    def must_estimate(self) -> list[str]:
         """Subspace parameters that must be estimated.
 
         Does not include parameters that are estimated in the PEtab
@@ -898,7 +905,7 @@ def must_estimate(self) -> List[str]:
         ]
 
     @property
-    def must_estimate_all(self) -> List[str]:
+    def must_estimate_all(self) -> list[str]:
         """All parameters that must be estimated in this subspace."""
         must_estimate_petab = [
             parameter_id
@@ -909,8 +916,8 @@ def must_estimate_all(self) -> List[str]:
 
     def get_estimated(
         self,
-        additional_parameters: Optional[TYPE_PARAMETER_DICT] = None,
-    ) -> List[str]:
+        additional_parameters: TYPE_PARAMETER_DICT | None = None,
+    ) -> list[str]:
         """Get the IDs of parameters that are estimated.
 
         Args:
@@ -922,40 +929,42 @@ def get_estimated(
             The parameter IDs.
         """
         raise NotImplementedError
-        parameters = []
-        for parameter_id, parameter_value in self.parameters_all.items():
-            if additional_parameters.get(parameter_id, None) == ESTIMATE:
-                parameters.append(parameter_id)
-                continue
 
-            if parameter_id in additional_parameters:
-                # Presumably not estimated.
-                continue
-
-        old_estimated_all = {
-            parameter_id
-            for parameter_id, parameter_values in self.parameters_all.items()
-            if (
-                # Predecessor model sets the parameter to be estimated
-                (
-                    candidate_space.predecessor_model.parameters.get(
-                        parameter_id, None
-                    )
-                    == ESTIMATE
-                )
-                or (
-                    # Predecessor model takes the default PEtab parameter
-                    parameter_id
-                    not in candidate_space.predecessor_model.parameters
-                    and
-                    # And the default PEtab parameter is estimated
-                    # The PEtab problem of this subspace and the
-                    # `candidate_space` is the same, as verified earlier with
-                    # `self.check_compatibility_stepwise_method`.
-                    self.petab_parameters[parameter_id] == [ESTIMATE]
-                )
-            )
-        }
+        # parameters = []
+        # for parameter_id, parameter_value in self.parameters_all.items():
+        #     if additional_parameters.get(parameter_id, None) == ESTIMATE:
+        #         parameters.append(parameter_id)
+        #         continue
+        #
+        #     if parameter_id in additional_parameters:
+        #         # Presumably not estimated.
+        #         continue
+        #
+        # old_estimated_all = {
+        #     parameter_id
+        #     for parameter_id, parameter_values in self.parameters_all.items()
+        #     if
+        #     (
+        #         # Predecessor model sets the parameter to be estimated
+        #         (
+        #             candidate_space.predecessor_model.parameters.get(
+        #                 parameter_id, None
+        #             )
+        #             == ESTIMATE
+        #         )
+        #         or (
+        #             # Predecessor model takes the default PEtab parameter
+        #             parameter_id
+        #             not in candidate_space.predecessor_model.parameters
+        #             and
+        #             # And the default PEtab parameter is estimated
+        #             # The PEtab problem of this subspace and the
+        #             # `candidate_space` is the same, as verified earlier with
+        #             # `self.check_compatibility_stepwise_method`.
+        #             self.petab_parameters[parameter_id] == [ESTIMATE]
+        #         )
+        #     )
+        # }
 
     def __len__(self) -> int:
         """Get the number of models in this subspace."""
@@ -965,7 +974,7 @@ def __len__(self) -> int:
 
 
 def decompress_parameter_values(
-    values: Union[float, int, str],
+    values: float | int | str,
 ) -> TYPE_PARAMETER_OPTIONS:
     """Decompress parameter values.
 
@@ -978,7 +987,7 @@ def decompress_parameter_values(
     Returns:
         Parameter values, decompressed into a list.
     """
-    if isinstance(values, (float, int)):
+    if isinstance(values, float | int):
         return [values]
 
     parameter_strings = list(values.split(PARAMETER_VALUE_DELIMITER))
diff --git a/petab_select/petab.py b/petab_select/petab.py
index c827f93d..8d370c8e 100644
--- a/petab_select/petab.py
+++ b/petab_select/petab.py
@@ -1,9 +1,8 @@
 from pathlib import Path
-from typing import List, Optional
 
-import petab
+import petab.v1 as petab
 from more_itertools import one
-from petab.C import ESTIMATE, NOMINAL_VALUE
+from petab.v1.C import ESTIMATE, NOMINAL_VALUE
 
 from .constants import PETAB_ESTIMATE_FALSE, TYPE_PARAMETER_DICT, TYPE_PATH
 
@@ -26,14 +25,14 @@ class PetabMixin:
 
     def __init__(
         self,
-        petab_yaml: Optional[TYPE_PATH] = None,
-        petab_problem: Optional[petab.Problem] = None,
+        petab_yaml: TYPE_PATH | None = None,
+        petab_problem: petab.Problem | None = None,
         parameters_as_lists: bool = False,
     ):
         if petab_yaml is None and petab_problem is None:
             raise ValueError(
-                'Please supply at least one of either the location of the '
-                'PEtab problem YAML file, or an instance of the PEtab problem.'
+                "Please supply at least one of either the location of the "
+                "PEtab problem YAML file, or an instance of the PEtab problem."
             )
         self.petab_yaml = petab_yaml
         if self.petab_yaml is not None:
@@ -57,7 +56,7 @@ def __init__(
             }
 
     @property
-    def petab_parameter_ids_estimated(self) -> List[str]:
+    def petab_parameter_ids_estimated(self) -> list[str]:
         """Get the IDs of all estimated parameters.
 
         Returns:
@@ -70,7 +69,7 @@ def petab_parameter_ids_estimated(self) -> List[str]:
         ]
 
     @property
-    def petab_parameter_ids_fixed(self) -> List[str]:
+    def petab_parameter_ids_fixed(self) -> list[str]:
         """Get the IDs of all fixed parameters.
 
         Returns:
diff --git a/petab_select/plot.py b/petab_select/plot.py
new file mode 100644
index 00000000..08137c82
--- /dev/null
+++ b/petab_select/plot.py
@@ -0,0 +1,815 @@
+"""Visualization routines for model selection."""
+
+import warnings
+from typing import Any
+
+import matplotlib
+import matplotlib.cm
+import matplotlib.colors
+import matplotlib.pyplot as plt
+import matplotlib.ticker
+import networkx as nx
+import numpy as np
+import upsetplot
+from more_itertools import one
+from toposort import toposort
+
+from .constants import VIRTUAL_INITIAL_MODEL, Criterion
+from .model import Model, ModelHash
+
+RELATIVE_LABEL_FONTSIZE = -2
+NORMAL_NODE_COLOR = "darkgrey"
+
+
+__all__ = [
+    "bar_criterion_vs_models",
+    "graph_history",
+    "graph_iteration_layers",
+    "line_selected",
+    "scatter_criterion_vs_n_estimated",
+    "upset",
+]
+
+
+def get_model_hashes(models: list[Model]) -> dict[str, Model]:
+    """Get the model hash to model mapping.
+
+    Args:
+        models:
+            The models.
+
+    Returns:
+        The mapping.
+    """
+    model_hashes = {model.get_hash(): model for model in models}
+    return model_hashes
+
+
+def get_selected_models(
+    models: list[Model],
+    criterion: Criterion,
+) -> list[Model]:
+    """Get the models that strictly improved on their predecessors.
+
+    Args:
+        models:
+            The models.
+        criterion:
+            The criterion
+
+    Returns:
+        The strictly improving models.
+    """
+    criterion_value0 = np.inf
+    model0 = None
+    model_hashes = get_model_hashes(models)
+    for model in models:
+        criterion_value = model.get_criterion(criterion)
+        if criterion_value < criterion_value0:
+            criterion_value0 = criterion_value
+            model0 = model
+
+    selected_models = [model0]
+    while True:
+        model0 = selected_models[-1]
+        model1 = model_hashes.get(model0.predecessor_model_hash, None)
+        if model1 is None:
+            break
+        selected_models.append(model1)
+
+    return selected_models[::-1]
+
+
+def get_relative_criterion_values(
+    criterion_values: dict[str, float] | list[float],
+) -> dict[str, float] | list[float]:
+    values = criterion_values
+    if isinstance(criterion_values, dict):
+        values = criterion_values.values()
+
+    value0 = np.inf
+    for value in values:
+        if value < value0:
+            value0 = value
+
+    if isinstance(criterion_values, dict):
+        return {k: v - value0 for k, v in criterion_values.items()}
+    return [v - value0 for v in criterion_values]
+
+
+def upset(
+    models: list[Model], criterion: Criterion
+) -> dict[str, matplotlib.axes.Axes | None]:
+    """Plot an UpSet plot of estimated parameters and criterion.
+
+    Args:
+        models:
+            The models.
+        criterion:
+            The criterion.
+
+    Returns:
+        The plot axes (see documentation from the `upsetplot <https://upsetplot.readthedocs.io/>`__ package).
+    """
+    # Get delta criterion values
+    values = np.array(
+        get_relative_criterion_values(
+            [model.get_criterion(criterion) for model in models]
+        )
+    )
+
+    # Sort by criterion value
+    index = np.argsort(values)
+    values = values[index]
+    labels = [models[i].get_estimated_parameter_ids_all() for i in index]
+
+    with warnings.catch_warnings():
+        # TODO remove warnings context manager when fixed in upsetplot package
+        warnings.simplefilter(action="ignore", category=FutureWarning)
+        series = upsetplot.from_memberships(
+            labels,
+            data=values,
+        )
+        axes = upsetplot.plot(
+            series, totals_plot_elements=0, with_lines=False, sort_by="input"
+        )
+    axes["intersections"].set_ylabel(r"$\Delta$" + criterion)
+    return axes
+
+
+def line_selected(
+    models: list[Model],
+    criterion: Criterion,
+    relative: bool = True,
+    fz: int = 14,
+    labels: dict[str, str] = None,
+    ax: plt.Axes = None,
+) -> plt.Axes:
+    """Plot the improvement in criterion across iterations.
+
+    Args:
+        models:
+            A list of all models. The selected models will be inferred from the
+            best model and its chain of predecessor model hashes.
+        criterion:
+            The criterion by which models are selected.
+        relative:
+            If ``True``, criterion values are plotted relative to the lowest
+            criterion value.
+        fz:
+            fontsize
+        labels:
+            A dictionary of model labels, where keys are model hashes, and
+            values are model labels, for plotting. If a model label is not
+            provided, it will be generated from its model ID.
+        ax:
+            The axis to use for plotting.
+
+    Returns:
+        The plot axes.
+    """
+    models = get_selected_models(models=models, criterion=criterion)
+
+    if labels is None:
+        labels = {}
+
+    # FIGURE
+    if ax is None:
+        _, ax = plt.subplots(figsize=(5, 4))
+    linewidth = 3
+
+    models = [model for model in models if model != VIRTUAL_INITIAL_MODEL]
+
+    criterion_values = {
+        labels.get(model.get_hash(), model.model_id): model.get_criterion(
+            criterion
+        )
+        for model in models
+    }
+    if relative:
+        criterion_values = get_relative_criterion_values(criterion_values)
+
+    ax.plot(
+        criterion_values.keys(),
+        criterion_values.values(),
+        linewidth=linewidth,
+        color=NORMAL_NODE_COLOR,
+        marker="x",
+        markersize=10,
+        markeredgewidth=2,
+        markeredgecolor="red",
+        # edgecolor='k'
+    )
+
+    ax.get_xticks()
+    ax.set_xticks(list(range(len(criterion_values))))
+    ax.set_ylabel((r"$\Delta$" if relative else "") + criterion, fontsize=fz)
+    # could change to compared_model_ids, if all models are plotted
+    ax.set_xticklabels(
+        criterion_values.keys(),
+        fontsize=fz + RELATIVE_LABEL_FONTSIZE,
+    )
+    for tick in ax.yaxis.get_major_ticks():
+        tick.label1.set_fontsize(fz + RELATIVE_LABEL_FONTSIZE)
+    ytl = ax.get_yticks()
+    ax.set_ylim([min(ytl), max(ytl)])
+    # removing top and right borders
+    ax.spines["top"].set_visible(False)
+    ax.spines["right"].set_visible(False)
+    return ax
+
+
+def graph_history(
+    models: list[Model],
+    criterion: Criterion = None,
+    labels: dict[str, str] = None,
+    colors: dict[str, str] = None,
+    draw_networkx_kwargs: dict[str, Any] = None,
+    relative: bool = True,
+    spring_layout_kwargs: dict[str, Any] = None,
+    ax: plt.Axes = None,
+) -> plt.Axes:
+    """Plot all calibrated models in the model space, as a directed graph.
+
+    Args:
+        models:
+            A list of models.
+        criterion:
+            The criterion.
+        labels:
+            A dictionary of model labels, where keys are model hashes, and
+            values are model labels, for plotting. If a model label is not
+            provided, it will be generated from its model ID.
+        colors:
+            Colors for each model, using their labels.
+        draw_networkx_kwargs:
+            Forwarded to ``networkx.draw_networkx``.
+        relative:
+            If ``True``, criterion values are offset by the minimum criterion
+            value.
+        spring_layout_kwargs:
+            Forwarded to ``networkx.spring_layout``.
+        ax:
+            The axis to use for plotting.
+
+
+    Returns:
+        The plot axes.
+    """
+    default_spring_layout_kwargs = {"k": 1, "iterations": 20}
+    if spring_layout_kwargs is None:
+        spring_layout_kwargs = default_spring_layout_kwargs
+    model_hashes = get_model_hashes(models)
+
+    criterion_values = {
+        model_hash: model.get_criterion(criterion)
+        for model_hash, model in model_hashes.items()
+    }
+    if relative:
+        criterion_values = get_relative_criterion_values(criterion_values)
+
+    if labels is None:
+        labels = {
+            model_hash: model.model_id
+            + (
+                f"\n{criterion_values[model_hash]:.2f}"
+                if criterion is not None
+                else ""
+            )
+            for model_hash, model in model_hashes.items()
+        }
+    labels = labels.copy()
+    labels[VIRTUAL_INITIAL_MODEL] = "Virtual\nInitial\nModel"
+
+    G = nx.DiGraph()
+    edges = []
+    for model in models:
+        predecessor_model_hash = model.predecessor_model_hash
+        if predecessor_model_hash is not None:
+            from_ = labels.get(predecessor_model_hash, predecessor_model_hash)
+            # may only not be the case for
+            # COMPARED_MODEL_ID == INITIAL_VIRTUAL_MODEL
+            if predecessor_model_hash in model_hashes:
+                predecessor_model = model_hashes[predecessor_model_hash]
+                from_ = labels.get(
+                    predecessor_model.get_hash(),
+                    predecessor_model.model_id,
+                )
+        else:
+            raise NotImplementedError(
+                "Plots for models with `None` as their predecessor model are "
+                "not yet implemented."
+            )
+            from_ = "None"
+        to = labels.get(model.get_hash(), model.model_id)
+        edges.append((from_, to))
+
+    G.add_edges_from(edges)
+    default_draw_networkx_kwargs = {
+        "node_color": NORMAL_NODE_COLOR,
+        "arrowstyle": "-|>",
+        "node_shape": "s",
+        "node_size": 2500,
+        "edgecolors": "k",
+    }
+    if draw_networkx_kwargs is None:
+        draw_networkx_kwargs = default_draw_networkx_kwargs
+    if colors is not None:
+        if label_diff := set(colors).difference(list(G)):
+            raise ValueError(
+                "Colors were provided for the following model labels, but "
+                f"these are not in the graph: {label_diff}"
+            )
+
+        node_colors = [
+            colors.get(model_label, default_draw_networkx_kwargs["node_color"])
+            for model_label in list(G)
+        ]
+        draw_networkx_kwargs.update({"node_color": node_colors})
+
+    if ax is None:
+        _, ax = plt.subplots(figsize=(12, 12))
+
+    pos = nx.spring_layout(G, **spring_layout_kwargs)
+    nx.draw_networkx(G, pos, ax=ax, **draw_networkx_kwargs)
+
+    return ax
+
+
+def bar_criterion_vs_models(
+    models: list[Model],
+    criterion: Criterion = None,
+    labels: dict[str, str] = None,
+    relative: bool = True,
+    ax: plt.Axes = None,
+    bar_kwargs: dict[str, Any] = None,
+    colors: dict[str, str] = None,
+) -> plt.Axes:
+    """Plot all calibrated models and their criterion value.
+
+    Args:
+        models:
+            A list of models.
+        criterion:
+            The criterion.
+        labels:
+            A dictionary of model labels, where keys are model hashes, and
+            values are model labels, for plotting. If a model label is not
+            provided, it will be generated from its model ID.
+        relative:
+            If `True`, criterion values are offset by the minimum criterion
+            value.
+        ax:
+            The axis to use for plotting.
+        bar_kwargs:
+            Passed to the matplotlib `ax.bar` call.
+        colors:
+            Custom colors. Keys are model hashes or ``labels`` (if provided),
+            values are matplotlib colors.
+
+    Returns:
+        The plot axes.
+    """
+    model_hashes = get_model_hashes(models)
+
+    if bar_kwargs is None:
+        bar_kwargs = {}
+
+    if labels is None:
+        labels = {
+            model_hash: model.model_id
+            for model_hash, model in model_hashes.items()
+        }
+
+    if ax is None:
+        _, ax = plt.subplots()
+
+    criterion_values = {
+        labels.get(model.get_hash(), model.model_id): model.get_criterion(
+            criterion
+        )
+        for model in models
+    }
+
+    if colors is not None:
+        if label_diff := set(colors).difference(criterion_values):
+            raise ValueError(
+                "Colors were provided for the following model labels, but "
+                f"these are not in the graph: {label_diff}"
+            )
+
+        bar_kwargs["color"] = [
+            colors.get(model_label, NORMAL_NODE_COLOR)
+            for model_label in criterion_values
+        ]
+
+    if relative:
+        criterion_values = get_relative_criterion_values(criterion_values)
+    ax.bar(criterion_values.keys(), criterion_values.values(), **bar_kwargs)
+    ax.set_xlabel("Model labels")
+    ax.set_ylabel(
+        (r"$\Delta$" if relative else "") + criterion,
+    )
+
+    return ax
+
+
+def scatter_criterion_vs_n_estimated(
+    models: list[Model],
+    criterion: Criterion = None,
+    relative: bool = True,
+    ax: plt.Axes = None,
+    colors: dict[str, str] = None,
+    labels: dict[str, str] = None,
+    scatter_kwargs: dict[str, str] = None,
+    max_jitter: float = 0.2,
+) -> plt.Axes:
+    """Plot criterion values against number of estimated parameters.
+
+    Args:
+        models:
+            A list of models.
+        criterion:
+            The criterion.
+        relative:
+            If `True`, criterion values are offset by the minimum criterion
+            value.
+        ax:
+            The axis to use for plotting.
+        colors:
+            Custom colors. Keys are model hashes or ``labels`` (if provided),
+            values are matplotlib colors.
+        labels:
+            A dictionary of model labels, where keys are model hashes, and
+            values are model labels, for plotting. If a model label is not
+            provided, it will be generated from its model ID.
+        scatter_kwargs:
+            Forwarded to ``matplotlib.axes.Axes.scatter``.
+        max_jitter:
+            Add noise to distinguish models with the same number of parameters
+            and similar criterion values. This is a positive value that is the
+            maximal difference to the original value.
+
+    Returns:
+        The plot axes.
+    """
+    model_hashes = get_model_hashes(models)
+
+    labels = {
+        model_hash: labels.get(model.model_id, model.model_id)
+        for model_hash, model in model_hashes.items()
+    }
+
+    if scatter_kwargs is None:
+        scatter_kwargs = {}
+
+    if colors is not None:
+        if label_diff := set(colors).difference(labels.values()):
+            raise ValueError(
+                "Colors were provided for the following model labels, but "
+                f"these are not in the graph: {label_diff}"
+            )
+        scatter_kwargs["c"] = [
+            colors.get(model_label, NORMAL_NODE_COLOR)
+            for model_label in labels.values()
+        ]
+
+    n_estimated = []
+    criterion_values = []
+    for model in models:
+        n_estimated.append(len(model.get_estimated_parameter_ids_all()))
+        criterion_values.append(model.get_criterion(criterion))
+    if relative:
+        criterion_values = get_relative_criterion_values(criterion_values)
+
+    if max_jitter:
+        n_estimated = np.array(n_estimated, dtype=float)
+        rng = np.random.default_rng()
+        n_estimated += rng.uniform(
+            -max_jitter, max_jitter, size=n_estimated.size
+        )
+
+    if ax is None:
+        _, ax = plt.subplots()
+
+    ax.scatter(
+        n_estimated,
+        criterion_values,
+        **scatter_kwargs,
+    )
+
+    ax.xaxis.set_major_locator(matplotlib.ticker.MaxNLocator(integer=True))
+
+    ax.set_xlabel("Number of estimated parameters")
+    ax.set_ylabel(
+        (r"$\Delta$" if relative else "") + criterion,
+    )
+
+    return ax
+
+
+def graph_iteration_layers(
+    models: list[Model],
+    criterion: Criterion,
+    labels: dict[str, str] = None,
+    relative: bool = True,
+    ax: plt.Axes = None,
+    draw_networkx_kwargs: dict[str, Any] | None = None,
+    # colors: Dict[str, str] = None,
+    parameter_labels: dict[str, str] = None,
+    augment_labels: bool = True,
+    colorbar_mappable: matplotlib.cm.ScalarMappable = None,
+    # use_tex: bool = True,
+) -> plt.Axes:
+    """Graph the models of each iteration of model selection.
+
+    Args:
+        models:
+            A list of models.
+        criterion:
+            The criterion.
+        labels:
+            A dictionary of model labels, where keys are model hashes, and
+            values are model labels, for plotting. If a model label is not
+            provided, it will be generated from its model ID.
+        relative:
+            If ``True``, criterion values are offset by the minimum criterion
+            value.
+        ax:
+            The axis to use for plotting.
+        draw_networkx_kwargs:
+            Passed to the ``networkx.draw_networkx`` call.
+        parameter_labels:
+            A dictionary of parameter labels, where keys are parameter IDs, and
+            values are parameter labels, for plotting. Defaults to parameter IDs.
+        augment_labels:
+            If ``True``, provided labels will be augmented with the relative
+            change in parameters.
+        colorbar_mappable:
+            Customize the colors.
+            See documentation for the `mappable` argument of
+            ``matplotlib.pyplot.colorbar``.
+
+    Returns:
+        The plot axes.
+    """
+
+    if ax is None:
+        _, ax = plt.subplots(figsize=(20, 10))
+
+    model_hashes = {model.get_hash(): model for model in models}
+
+    default_draw_networkx_kwargs = {
+        #'node_color': NORMAL_NODE_COLOR,
+        "arrowstyle": "-|>",
+        "node_shape": "s",
+        "node_size": 250,
+        "edgecolors": "k",
+    }
+    if draw_networkx_kwargs is None:
+        draw_networkx_kwargs = default_draw_networkx_kwargs
+
+    ancestry = {
+        model.get_hash(): model.predecessor_model_hash for model in models
+    }
+    ancestry_as_set = {k: {v} for k, v in ancestry.items()}
+    ordering = [list(hashes) for hashes in toposort(ancestry_as_set)]
+
+    model_estimated_parameters = {
+        model.get_hash(): set(model.estimated_parameters) for model in models
+    }
+    model_criterion_values = None
+    model_criterion_values = {
+        model.get_hash(): model.get_criterion(criterion) for model in models
+    }
+
+    min_criterion_value = min(model_criterion_values.values())
+    model_criterion_values = {
+        k: v - min_criterion_value for k, v in model_criterion_values.items()
+    }
+
+    model_parameter_diffs = {
+        model.get_hash(): (
+            (set(), set())
+            if model.predecessor_model_hash not in model_estimated_parameters
+            else (
+                model_estimated_parameters[model.get_hash()].difference(
+                    model_estimated_parameters[model.predecessor_model_hash]
+                ),
+                model_estimated_parameters[
+                    model.predecessor_model_hash
+                ].difference(model_estimated_parameters[model.get_hash()]),
+            )
+        )
+        for model in models
+    }
+
+    labels = labels or {}
+    labels = (
+        labels
+        | {
+            model.get_hash(): model.model_id
+            for model in models
+            if model.get_hash() not in labels
+        }
+        | {
+            ModelHash.from_hash(
+                model.predecessor_model_hash
+            ): model.predecessor_model_hash
+            for model in models
+            if ModelHash.from_hash(model.predecessor_model_hash) not in labels
+        }
+    )
+    if augment_labels:
+
+        class Identidict(dict):
+            def __init__(self, *args, **kwargs):
+                super().__init__(*args, **kwargs)
+
+            def __getitem__(self, key):
+                return key
+
+        if parameter_labels is None:
+            parameter_labels = Identidict()
+
+        model_added_parameters = {
+            model_hash: ",".join(
+                [
+                    parameter_labels[parameter_id]
+                    for parameter_id in sorted(
+                        model_parameter_diffs[model_hash][0]
+                    )
+                ]
+            )
+            for model_hash in model_estimated_parameters
+        }
+        model_removed_parameters = {
+            model_hash: ",".join(
+                [
+                    parameter_labels[parameter_id]
+                    for parameter_id in sorted(
+                        model_parameter_diffs[model_hash][1]
+                    )
+                ]
+            )
+            for model_hash in model_estimated_parameters
+        }
+
+        labels = {
+            model_hash: (
+                label0
+                if model_hash == ModelHash.from_hash(VIRTUAL_INITIAL_MODEL)
+                else "\n".join(
+                    [
+                        label0,
+                        "+ {"
+                        + model_added_parameters.get(model_hash, "")
+                        + "}",
+                        "- {"
+                        + model_removed_parameters.get(model_hash, "")
+                        + "}",
+                    ]
+                )
+            )
+            for model_hash, label0 in labels.items()
+        }
+
+    if colorbar_mappable is None:
+        norm = matplotlib.colors.Normalize(
+            vmin=min(model_criterion_values.values()),
+            vmax=max(model_criterion_values.values()),
+        )
+        cmap = matplotlib.colors.LinearSegmentedColormap.from_list(
+            "", ["green", "white"]
+        )
+        colorbar_mappable = matplotlib.cm.ScalarMappable(norm=norm, cmap=cmap)
+    cbar = ax.get_figure().colorbar(colorbar_mappable, ax=ax)
+    cbar.ax.set_title(r"$\Delta$" + criterion)
+
+    G = nx.DiGraph(
+        [
+            (predecessor, successor)
+            for successor, predecessor in ancestry.items()
+        ]
+    )
+    # FIXME change positions so that top edge of topmost node, and bottom edge
+    # of bottommost nodes, are at 1 and 0, respectively
+    X = [
+        (0 if len(ordering) == 1 else 1 / (len(ordering) - 1)) * i
+        for i in range(0, len(ordering))
+    ]
+    Y = [
+        [
+            (0 if len(layer) == 1 else 1 / (len(layer) - 1)) * j
+            for j in range(0, len(layer))
+        ]
+        for layer in ordering
+    ]
+
+    pos = {
+        labels[model_hash]: (X[i], Y[i][j])
+        for i, layer in enumerate(ordering)
+        for j, model_hash in enumerate(layer)
+    }
+    nx.relabel_nodes(G, mapping=labels, copy=False)
+
+    G_hashes = [
+        one([k for k, v in labels.items() if v == label]) for label in G.nodes
+    ]
+    node_colors = [
+        (
+            colorbar_mappable.to_rgba(model_criterion_values[model_hash])
+            if model_hash in model_criterion_values
+            else NORMAL_NODE_COLOR
+        )
+        for model_hash in G_hashes
+    ]
+
+    nx.draw_networkx(
+        G, pos, ax=ax, node_color=node_colors, **draw_networkx_kwargs
+    )
+
+    # Add `n=...` labels
+    N = [len(y) for y in Y]
+    for x, n in zip(X, N, strict=False):
+        ax.annotate(
+            f"n={n}",
+            xy=(x, 1.1),
+            fontsize=draw_networkx_kwargs.get("font_size", 20),
+            ha="center",
+        )
+
+    ## Get selected parameter IDs
+    ## TODO move this logic elsewhere
+    # selected_hashes = set(ancestry.values())
+    # selected_models = {}
+    # for model in models:
+    #    if model.get_hash() in selected_hashes:
+    #        selected_models[model.get_hash()] = model
+
+    # selected_parameters = {
+    #    model_hash: sorted(model.estimated_parameters)
+    #    for model_hash, model in selected_models.items()
+    # }
+
+    # selected_order = [
+    #    [model_hash for model_hash in layer if model_hash in selected_models]
+    #    for layer in ordering
+    # ]
+    # selected_order = [
+    #    None if not model_hash else one(model_hash)
+    #    for model_hash in selected_order
+    # ]
+
+    # selected_parameter_ids = []
+    # estimated0 = None
+    # model_hash = None
+    # for model_hash in selected_order:
+    #    if model_hash is None:
+    #        selected_parameter_ids.append('')
+    #        continue
+    #    if estimated0 is not None:
+    #        new_parameter_ids = list(
+    #            set(selected_parameters[model_hash]).symmetric_difference(
+    #                estimated0
+    #            )
+    #        )
+    #        new_parameter_names = []
+    #        for new_parameter_id in new_parameter_ids:
+    #            # Default to parameter ID, use parameter name if available
+    #            new_parameter_name = new_parameter_id
+    #            if (
+    #                PARAMETER_NAME
+    #                in selected_models[
+    #                    model_hash
+    #                ].petab_problem.parameter_df.columns
+    #            ):
+    #                petab_parameter_name = selected_models[
+    #                    model_hash
+    #                ].petab_problem.parameter_df.loc[
+    #                    new_parameter_id, PARAMETER_NAME
+    #                ]
+    #                if not pd.isna(petab_parameter_name):
+    #                    new_parameter_name = petab_parameter_name
+    #            new_parameter_names.append(new_parameter_name)
+    #        new_parameter_names = [
+    #            new_parameter_name.replace('\\\\rightarrow ', '->')
+    #            for new_parameter_name in new_parameter_names
+    #        ]
+    #        selected_parameter_ids.append(sorted(new_parameter_names))
+    #    else:
+    #        selected_parameter_ids.append([''])
+    #    estimated0 = selected_parameters[model_hash]
+
+    ## Add labels for selected parameters
+    # for x, label in zip(X, selected_parameter_ids):
+    #    ax.annotate(
+    #        "\n".join(label),
+    #        xy=(x, 1.15),
+    #        fontsize=draw_networkx_kwargs.get('font_size', 20),
+    #    )
+
+    # Set margins for the axes so that nodes aren't clipped
+    ax.margins(0.15)
+    ax.axis("off")
+    return ax
diff --git a/petab_select/problem.py b/petab_select/problem.py
index 7b97fb19..c7e20146 100644
--- a/petab_select/problem.py
+++ b/petab_select/problem.py
@@ -1,8 +1,9 @@
 """The model selection problem class."""
-import abc
+
+from collections.abc import Callable, Iterable
 from functools import partial
 from pathlib import Path
-from typing import Any, Callable, Dict, Iterable, Optional, Union
+from typing import Any
 
 import yaml
 
@@ -13,19 +14,20 @@
     METHOD,
     MODEL_SPACE_FILES,
     PREDECESSOR_MODEL,
+    PROBLEM_ID,
     VERSION,
     Criterion,
     Method,
 )
-from .model import Model, default_compare
+from .model import Model, ModelHash, default_compare
 from .model_space import ModelSpace
 
 __all__ = [
-    'Problem',
+    "Problem",
 ]
 
 
-class Problem(abc.ABC):
+class Problem:
     """Handle everything related to the model selection problem.
 
     Attributes:
@@ -34,9 +36,8 @@ class Problem(abc.ABC):
         calibrated_models:
             Calibrated models. Will be used to augment the model selection problem (e.g.
             by excluding them from the model space).
-            FIXME(dilpath) refactor out
         candidate_space_arguments:
-            Custom options that are used to construct the candidate space.
+            Arguments are forwarded to the candidate space constructor.
         compare:
             A method that compares models by selection criterion. See
             :func:`petab_select.model.default_compare` for an example.
@@ -49,9 +50,6 @@ class Problem(abc.ABC):
         yaml_path:
             The location of the selection problem YAML file. Used for relative
             paths that exist in e.g. the model space files.
-
-            TODO should the relative paths be relative to the YAML or the file that contains them?
-
     """
 
     """
@@ -62,20 +60,27 @@ class Problem(abc.ABC):
             Reason for not saving:
                 Essentially reproducible from :attr:`Problem.method` and
                 :attr:`Problem.calibrated_models`.
+    FIXME(dilpath) refactor calibrated_models out, move to e.g. candidate
+                   space args
+            TODO should the relative paths be relative to the YAML or the file that contains them?
+                 problem relative to file that contains them
+
     """
 
     def __init__(
         self,
         model_space: ModelSpace,
-        candidate_space_arguments: Dict[str, Any] = None,
+        candidate_space_arguments: dict[str, Any] = None,
         compare: Callable[[Model, Model], bool] = None,
         criterion: Criterion = None,
+        problem_id: str = None,
         method: str = None,
         version: str = None,
-        yaml_path: Union[Path, str] = None,
+        yaml_path: Path | str = None,
     ):
         self.model_space = model_space
         self.criterion = criterion
+        self.problem_id = problem_id
         self.method = method
         self.version = version
         self.yaml_path = Path(yaml_path)
@@ -88,7 +93,15 @@ def __init__(
         if self.compare is None:
             self.compare = partial(default_compare, criterion=self.criterion)
 
-    def get_path(self, relative_path: Union[str, Path]) -> Path:
+    def __str__(self):
+        return (
+            f"YAML: {self.yaml_path}\n"
+            f"Method: {self.method}\n"
+            f"Criterion: {self.criterion}\n"
+            f"Version: {self.version}\n"
+        )
+
+    def get_path(self, relative_path: str | Path) -> Path:
         """Get the path to a resource, from a relative path.
 
         Args:
@@ -133,8 +146,8 @@ def exclude_model_hashes(
 
     @staticmethod
     def from_yaml(
-        yaml_path: Union[str, Path],
-    ) -> 'Problem':
+        yaml_path: str | Path,
+    ) -> "Problem":
         """Generate a problem from a PEtab Select problem YAML file.
 
         Args:
@@ -145,13 +158,13 @@ def from_yaml(
             A `Problem` instance.
         """
         yaml_path = Path(yaml_path)
-        with open(yaml_path, 'r') as f:
+        with open(yaml_path) as f:
             problem_specification = yaml.safe_load(f)
 
         if not problem_specification.get(MODEL_SPACE_FILES, []):
             raise KeyError(
-                'The model selection problem specification file is missing '
-                'model space files.'
+                "The model selection problem specification file is missing "
+                "model space files."
             )
 
         model_space = ModelSpace.from_files(
@@ -173,6 +186,8 @@ def from_yaml(
         if criterion is not None:
             criterion = Criterion(criterion)
 
+        problem_id = problem_specification.get(PROBLEM_ID, None)
+
         candidate_space_arguments = problem_specification.get(
             CANDIDATE_SPACE_ARGUMENTS,
             None,
@@ -190,14 +205,15 @@ def from_yaml(
             criterion=criterion,
             # TODO refactor method to use enum
             method=problem_specification.get(METHOD, None),
+            problem_id=problem_id,
             version=problem_specification.get(VERSION, None),
             yaml_path=yaml_path,
         )
 
     def get_best(
         self,
-        models: Optional[Iterable[Model]],
-        criterion: Optional[Union[str, None]] = None,
+        models: list[Model] | dict[ModelHash, Model] | None,
+        criterion: str | None | None = None,
         compute_criterion: bool = False,
     ) -> Model:
         """Get the best model from a collection of models.
@@ -219,6 +235,8 @@ def get_best(
         Returns:
             The best model.
         """
+        if isinstance(models, dict):
+            models = list(models.values())
         if criterion is None:
             criterion = self.criterion
 
@@ -235,10 +253,31 @@ def get_best(
                 best_model = model
         if best_model is None:
             raise KeyError(
-                f'None of the supplied models have a value set for the criterion {criterion}.'
+                f"None of the supplied models have a value set for the criterion {criterion}."
             )
         return best_model
 
+    def model_hash_to_model(self, model_hash: str | ModelHash) -> Model:
+        """Get the model that matches a model hash.
+
+        Args:
+            model_hash:
+                The model hash.
+
+        Returns:
+            The model.
+        """
+        return ModelHash.from_hash(model_hash).get_model(
+            petab_select_problem=self,
+        )
+
+    def get_model(
+        self, model_subspace_id: str, model_subspace_indices: list[int]
+    ) -> Model:
+        return self.model_space.model_subspaces[
+            model_subspace_id
+        ].indices_to_model(model_subspace_indices)
+
     def new_candidate_space(
         self,
         *args,
@@ -255,6 +294,7 @@ def new_candidate_space(
         """
         if method is None:
             method = self.method
+        kwargs[CRITERION] = kwargs.get(CRITERION, self.criterion)
         candidate_space_class = method_to_candidate_space_class(method)
         candidate_space_arguments = (
             candidate_space_class.read_arguments_from_yaml_dict(
diff --git a/petab_select/ui.py b/petab_select/ui.py
index 204ffd35..f5ed1f10 100644
--- a/petab_select/ui.py
+++ b/petab_select/ui.py
@@ -1,45 +1,58 @@
 import copy
 from pathlib import Path
-from typing import Dict, List, Optional, Union
+from typing import Any
 
 import numpy as np
-import petab
+import petab.v1 as petab
 
 from .candidate_space import CandidateSpace, FamosCandidateSpace
 from .constants import (
-    INITIAL_MODEL_METHODS,
+    CANDIDATE_SPACE,
+    MODELS,
+    PREDECESSOR_MODEL,
+    TERMINATE,
     TYPE_PATH,
+    UNCALIBRATED_MODELS,
     VIRTUAL_INITIAL_MODEL,
     Criterion,
     Method,
 )
-from .model import Model, default_compare
+from .model import Model, ModelHash, default_compare
 from .problem import Problem
 
 __all__ = [
-    'candidates',
-    'model_to_petab',
-    'models_to_petab',
-    'best',
-    'write_summary_tsv',
+    "start_iteration",
+    "end_iteration",
+    "model_to_petab",
+    "models_to_petab",
+    "get_best",
+    "write_summary_tsv",
 ]
 
 
-def candidates(
+def get_iteration(candidate_space: CandidateSpace) -> dict[str, Any]:
+    return {
+        CANDIDATE_SPACE: candidate_space,
+        UNCALIBRATED_MODELS: candidate_space.models,
+        PREDECESSOR_MODEL: candidate_space.get_predecessor_model(),
+    }
+
+
+def start_iteration(
     problem: Problem,
-    candidate_space: Optional[CandidateSpace] = None,
-    limit: Union[float, int] = np.inf,
-    limit_sent: Union[float, int] = np.inf,
-    calibrated_models: Optional[Dict[str, Model]] = None,
-    newly_calibrated_models: Optional[Dict[str, Model]] = None,
-    excluded_models: Optional[List[Model]] = None,
-    excluded_model_hashes: Optional[List[str]] = None,
-    criterion: Optional[Criterion] = None,
+    candidate_space: CandidateSpace | None = None,
+    limit: float | int = np.inf,
+    limit_sent: float | int = np.inf,
+    excluded_hashes: list[ModelHash] | None = None,
+    criterion: Criterion | None = None,
+    user_calibrated_models: list[Model] | dict[ModelHash, Model] | None = None,
 ) -> CandidateSpace:
     """Search the model space for candidate models.
 
-    A predecessor model is chosen from ``newly_calibrated_models`` if available,
-    otherwise from ``calibrated_models``, and is used for applicable methods.
+    The predecessor model can be specified in the `candidate_space`
+    (:func:`CandidateSpace.set_predecessor_model). If `candidate_space` is not
+    provided, then the predecessor model can be specified in `problem`
+    (:attr:`Problem.candidate_space_arguments`).
 
     Args:
         problem:
@@ -52,43 +65,49 @@ def candidates(
         limit_sent:
             The maximum number of models sent to the candidate space (which are possibly
             rejected and excluded).
-        calibrated_models:
-            All calibrated models in the model selection.
-        newly_calibrated_models:
-            All calibrated models in the most recent iteration of model
-            selection.
-        excluded_models:
-            Models that will be excluded from model subspaces during the search for
-            candidates.
-        excluded_model_hashes:
-            Hashes of models that will be excluded from model subspaces during the
-            search for candidates.
+        excluded_hashes:
+            Hashes of models that will be excluded from the candidate space.
         criterion:
             The criterion by which models will be compared. Defaults to the criterion
             defined in the PEtab Select problem.
+        user_calibrated_models:
+            Models that were already calibrated by the user. When supplied as a
+            `dict`, the keys are model hashes. If a model in the
+            candidates has the same hash as a model in
+            `user_calibrated_models`, then the candidate will be replaced with
+            the calibrated version. Calibration tools will only receive uncalibrated
+            models from this method.
 
     Returns:
-        The candidate space, which contains the candidate models.
+        A dictionary, with the following items:
+            :const:`petab_select.constants.CANDIDATE_SPACE`:
+                The candidate space.
+            :const:`petab_select.constants.MODELS`:
+                The uncalibrated models of the current iteration.
+    """
+    """
+    FIXME(dilpath)
+    - currently takes predecessor model from
+      candidate_space.previous_predecessor_model
+    - deprecate limit_sent? possibly unused by anyone
+    - add `Iteration` class to manage an iteration, append to
+      `CandidateSpace.iterations`?
     """
     do_search = True
     # FIXME might be difficult for a CLI tool to specify a specific predecessor
     #       model if their candidate space has models. Need a way to empty
     #       the candidate space of models... might be difficult with pickled
     #       candidate space objects/arguments?
-    if excluded_models is None:
-        excluded_models = []
-    if excluded_model_hashes is None:
-        excluded_model_hashes = []
-    if calibrated_models is None:
-        calibrated_models = {}
-    if newly_calibrated_models is None:
-        newly_calibrated_models = {}
-    calibrated_models.update(newly_calibrated_models)
-    if criterion is None:
-        criterion = problem.criterion
+    if excluded_hashes is None:
+        excluded_hashes = []
     if candidate_space is None:
         candidate_space = problem.new_candidate_space(limit=limit)
-    candidate_space.exclude_hashes(calibrated_models)
+
+    if criterion is None:
+        criterion = problem.criterion
+    if criterion is None:
+        raise ValueError("Please provide a criterion.")
+    candidate_space.criterion = criterion
 
     # Set the predecessor model to the previous predecessor model.
     predecessor_model = candidate_space.previous_predecessor_model
@@ -109,18 +128,24 @@ def candidates(
             # Dummy zero likelihood, which the predecessor model will
             # improve on after it's actually calibrated.
             predecessor_model.set_criterion(Criterion.LH, 0.0)
-            return candidate_space
+            candidate_space.set_iteration_user_calibrated_models(
+                user_calibrated_models=user_calibrated_models
+            )
+            return get_iteration(candidate_space=candidate_space)
 
         # Exclude the calibrated predecessor model.
         if not candidate_space.excluded(predecessor_model):
-            candidate_space.exclude(predecessor_model)
+            candidate_space.set_excluded_hashes(
+                predecessor_model,
+                extend=True,
+            )
 
     # Set the new predecessor_model from the initial model or
     # by calling ui.best to find the best model to jump to if
     # this is not the first step of the search.
-    if newly_calibrated_models:
+    if candidate_space.latest_iteration_calibrated_models:
         predecessor_model = problem.get_best(
-            newly_calibrated_models.values(),
+            candidate_space.latest_iteration_calibrated_models.values(),
             criterion=criterion,
         )
         # If the new predecessor model isn't better than the previous one,
@@ -134,44 +159,21 @@ def candidates(
         ):
             predecessor_model = candidate_space.previous_predecessor_model
 
-        try:
-            candidate_space.update_after_calibration(
-                calibrated_models=calibrated_models,
-                newly_calibrated_models=newly_calibrated_models,
-                criterion=criterion,
-            )
-        except StopIteration:
-            do_search = False
-
         # If candidate space not Famos then ignored.
         # Else, in case we jumped to most distant in this iteration, go into
         # calibration with only the model we've jumped to.
+        # TODO handle as proper `MostDistantCandidateSpace`
         if (
             isinstance(candidate_space, FamosCandidateSpace)
             and candidate_space.jumped_to_most_distant
         ):
-            return candidate_space
+            return get_iteration(candidate_space=candidate_space)
 
-    if (
-        predecessor_model is None
-        and candidate_space.method in INITIAL_MODEL_METHODS
-        and calibrated_models
-    ):
-        predecessor_model = problem.get_best(
-            models=calibrated_models.values(),
-            criterion=criterion,
-        )
     if predecessor_model is not None:
         candidate_space.reset(predecessor_model)
 
-    # TODO support excluding model IDs? should be faster but may have issues, e.g.:
-    #      - duplicate model IDs among multiple model subspaces
-    #      - perhaps less portable if model IDs are generated differently on different
-    #        computers
-    problem.model_space.exclude_models(models=excluded_models)
-    problem.model_space.exclude_model_hashes(
-        model_hashes=excluded_model_hashes
-    )
+    # FIXME store exclusions in candidate space only
+    problem.model_space.exclude_model_hashes(model_hashes=excluded_hashes)
     while do_search:
         problem.model_space.search(candidate_space, limit=limit_sent)
 
@@ -192,9 +194,7 @@ def candidates(
         if isinstance(candidate_space, FamosCandidateSpace):
             try:
                 candidate_space.update_after_calibration(
-                    calibrated_models=calibrated_models,
-                    newly_calibrated_models={},
-                    criterion=criterion,
+                    iteration_calibrated_models={},
                 )
                 continue
             except StopIteration:
@@ -206,13 +206,72 @@ def candidates(
 
     candidate_space.previous_predecessor_model = predecessor_model
 
-    return candidate_space
+    candidate_space.set_iteration_user_calibrated_models(
+        user_calibrated_models=user_calibrated_models
+    )
+    return get_iteration(candidate_space=candidate_space)
+
+
+def end_iteration(
+    candidate_space: CandidateSpace,
+    calibrated_models: list[Model] | dict[str, Model],
+) -> dict[str, dict[ModelHash, Model] | bool | CandidateSpace]:
+    """Finalize model selection iteration.
+
+    All models from the current iteration are provided to the calibration tool.
+    This includes user-calibrated models that the tool did not see until now.
+
+    A termination signal is also provided, if the model selection algorithm
+    ends.
+
+    Args:
+        candidate_space:
+            The candidate space.
+        calibrated_models:
+            The calibration results for the uncalibrated models of this
+            iteration.
+
+    Returns:
+        A dictionary, with the following items:
+            :const:`petab_select.constants.MODELS`:
+                All calibrated models for the current iteration as a
+                dictionary, where keys are model hashes, and values are models.
+            :const:`petab_select.constants.TERMINATE`:
+                Whether PEtab Select has decided to end the model selection,
+                as a boolean.
+    """
+    if isinstance(calibrated_models, list):
+        calibrated_models = {
+            model.get_hash(): model for model in calibrated_models
+        }
+
+    iteration_results = {
+        MODELS: candidate_space.get_iteration_calibrated_models(
+            calibrated_models=calibrated_models,
+            reset=True,
+        )
+    }
+
+    terminate = not iteration_results[MODELS]
+    try:
+        candidate_space.update_after_calibration(
+            iteration_calibrated_models=iteration_results[MODELS],
+        )
+    except StopIteration:
+        # e.g. FAMoS switch_method encountered "None", indicating end of model
+        # selection
+        terminate = True
+    iteration_results[TERMINATE] = terminate
+
+    iteration_results[CANDIDATE_SPACE] = candidate_space
+
+    return iteration_results
 
 
 def model_to_petab(
     model: Model,
-    output_path: Optional[TYPE_PATH] = None,
-) -> Dict[str, Union[petab.Problem, TYPE_PATH]]:
+    output_path: TYPE_PATH | None = None,
+) -> dict[str, petab.Problem | TYPE_PATH]:
     """Generate the PEtab problem for a model.
 
     Args:
@@ -229,9 +288,9 @@ def model_to_petab(
 
 
 def models_to_petab(
-    models: List[Model],
-    output_path_prefix: Optional[List[TYPE_PATH]] = None,
-) -> List[Dict[str, Union[petab.Problem, TYPE_PATH]]]:
+    models: list[Model],
+    output_path_prefix: list[TYPE_PATH] | None = None,
+) -> list[dict[str, petab.Problem | TYPE_PATH]]:
     """Generate the PEtab problems for a list of models.
 
     Args:
@@ -253,10 +312,10 @@ def models_to_petab(
     return result
 
 
-def best(
+def get_best(
     problem: Problem,
-    models: List[Model],
-    criterion: Optional[Union[str, None]] = None,
+    models: list[Model],
+    criterion: str | None | None = None,
 ) -> Model:
     """Get the best model from a list of models.
 
@@ -278,9 +337,9 @@ def best(
 
 def write_summary_tsv(
     problem: Problem,
-    candidate_space: Optional[CandidateSpace] = None,
-    previous_predecessor_model: Optional[Union[str, Model]] = None,
-    predecessor_model: Optional[Model] = None,
+    candidate_space: CandidateSpace | None = None,
+    previous_predecessor_model: str | Model | None = None,
+    predecessor_model: Model | None = None,
 ) -> None:
     if candidate_space.summary_tsv is None:
         return
@@ -329,7 +388,7 @@ def write_summary_tsv(
         and isinstance(candidate_space.predecessor_model, Model)
         and candidate_space.predecessor_model.predecessor_model_hash is None
     ):
-        with open(candidate_space.summary_tsv, 'r') as f:
+        with open(candidate_space.summary_tsv) as f:
             if sum(1 for _ in f) > 1:
                 method = Method.MOST_DISTANT
 
diff --git a/petab_select/version.py b/petab_select/version.py
index 6312a57b..86d0b954 100644
--- a/petab_select/version.py
+++ b/petab_select/version.py
@@ -1,2 +1,3 @@
 """Version of the model selection extension for PEtab."""
-__version__ = '0.1.13'
+
+__version__ = "0.1.13"
diff --git a/pyproject.toml b/pyproject.toml
index 50c68ba9..fb333fce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,16 +1,99 @@
 [build-system]
-requires = [
-    "setuptools>=42",
-    "wheel"
-]
+requires = ["setuptools>=64", "setuptools-scm>=8"]
 build-backend = "setuptools.build_meta"
 
-[tool.black]
-line-length = 79
-target-version = ['py37', 'py38', 'py39']
-skip-string-normalization = true
+[project]
+name = "petab_select"
+dynamic = ["version"]
+maintainers = [
+    {name = "Dilan Pathirana", email = "dilan.pathirana@uni-bonn.de"},
+]
+authors = [
+    {name = "The PEtab Select developers"},
+]
+description = "PEtab Select: an extension to PEtab for model selection."
+readme = "README.md"
+requires-python = ">=3.10"
+license = {text = "BSD-3-Clause"}
+dependencies = [
+    # TODO minimum versions
+    "more-itertools",
+    "numpy",
+    "pandas",
+    "petab",
+    "pyyaml",
+    "click",
+    "dill",
+]
+[project.optional-dependencies]
+plot = [
+    "matplotlib>=2.2.3",
+    "upsetplot",
+    "toposort",
+    "networkx",
+]
+test = [
+    "pytest >= 5.4.3",
+    "pytest-cov >= 2.10.0",
+    "amici >= 0.11.25",
+    "fides >= 0.7.5",
+    "pypesto > 0.2.13",
+    "tox >= 3.12.4",
+]
+doc = [
+    "sphinx>=8.1.3",
+    "sphinxcontrib-napoleon>=0.7",
+    "sphinx-markdown-tables>=0.0.15",
+    "sphinx-rtd-theme>=0.5.1",
+    "recommonmark>=0.7.1",
+    # pin until ubuntu comes with newer pandoc:
+    # /home/docs/checkouts/readthedocs.org/user_builds/petab-select/envs/63/lib/python3.11/site-packages/nbsphinx/__init__.py:1058: RuntimeWarning: You are using an unsupported version of pandoc (2.9.2.1).
+    # Your version must be at least (2.14.2) but less than (4.0.0).
+    "nbsphinx>=0.9.5",
+    "pandoc>=2.4",
+    "nbconvert>=7.16.4",
+    "ipython>=7.21.0",
+    "readthedocs-sphinx-ext>=2.2.5",
+    "sphinx-autodoc-typehints",
+]
+
+[project.scripts]
+petab_select = "petab_select.cli:cli"
+
+[tool.setuptools_scm]
 
-[tool.isort]
-profile = "black"
-line_length = 79
-multi_line_output = 3
+[tool.ruff]
+line-length = 79
+exclude = ["amici_models"]
+extend-include = ["*.ipynb"]
+lint.ignore = [
+    # FIXME: we should be able to remove move of those
+    "D103", # Missing docstring in public function
+    "S101", # Use of assert detected
+    "E501", # Line too long
+    "F403", # star import
+    "T201", # print statement
+    "S301", # pickle module used
+    "S102", # Use of exec detected
+    "S307", # Use of possibly insecure function
+    "B006",
+    "E722",
+    "B904",
+    "B007",
+    "F841",
+]
+lint.select = [
+    "F",  # Pyflakes
+    "I",  # isort
+    # "D",  # pydocstyle (PEP 257)  FIXME enable after https://github.com/PEtab-dev/petab_select/pull/67
+    "S",  # flake8-bandit
+    "B",  # flake8-bugbear
+    "C4",  # flake8-comprehensions
+    "T20",  # flake8-print
+    "W",  # pycodestyle Warnings
+    "E",  # pycodestyle Errors
+    "UP",  # pyupgrade
+    # "ANN",  # flakes-annotations
+]
+[tool.ruff.lint.pydocstyle]
+convention = "pep257"
diff --git a/requirements_dev.txt b/requirements_dev.txt
index cb8ddec6..ee8cc8cf 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -1,3 +1,4 @@
+git+https://github.com/ICB-DCM/pyPESTO.git@develop#egg=pypesto
 tox >= 3.12.4
 pre-commit >= 2.10.1
 flake8 >= 4.0.1
diff --git a/setup.py b/setup.py
index 0d47670e..64091987 100644
--- a/setup.py
+++ b/setup.py
@@ -1,11 +1,9 @@
-import os
 import re
-import sys
 
 from setuptools import find_packages, setup
 
-org = 'PEtab-dev'
-repo = 'petab_select'
+org = "PEtab-dev"
+repo = "petab_select"
 
 
 def read(fname):
@@ -15,12 +13,11 @@ def read(fname):
 
 def absolute_links(txt):
     """Replace relative petab github links by absolute links."""
-
     raw_base = f"(https://raw.githubusercontent.com/{org}/{repo}/main/"
     embedded_base = f"(https://github.com/{org}/{repo}/tree/main/"
     # iterate over links
-    for var in re.findall(r'\[.*?\]\((?!http).*?\)', txt):
-        if re.match(r'.*?.(png|svg)\)', var):
+    for var in re.findall(r"\[.*?\]\((?!http).*?\)", txt):
+        if re.match(r".*?.(png|svg)\)", var):
             # link to raw file
             rep = var.replace("(", raw_base)
         else:
@@ -30,78 +27,12 @@ def absolute_links(txt):
     return txt
 
 
-# 3.7.1 for NumPy, 3.8 for `typing.get_args`
-minimum_python_version = '3.8'
-if sys.version_info < tuple(map(int, minimum_python_version.split('.'))):
-    sys.exit(f'PEtab Select requires Python >= {minimum_python_version}')
-
-# read version from file
-__version__ = ''
-version_file = os.path.join('petab_select', 'version.py')
-# sets __version__
-exec(read(version_file))  # pylint: disable=W0122 # nosec
-
-ENTRY_POINTS = {
-    'console_scripts': [
-        'petab_select = petab_select.cli:cli',
-    ]
-}
-
 # project metadata
 # noinspection PyUnresolvedReferences
 setup(
-    name='petab_select',
-    version=__version__,
-    description='PEtab Select: an extension to PEtab for model selection.',
-    long_description=absolute_links(read('README.md')),
+    long_description=absolute_links(read("README.md")),
     long_description_content_type="text/markdown",
-    # author='The PEtab Select developers',
-    # author_email='dilan.pathirana@uni-bonn.de',
-    url=f'https://github.com/{org}/{repo}',
-    packages=find_packages(exclude=['doc*', 'test*']),
-    install_requires=[
-        # TODO minimum versions
-        'more-itertools',
-        'numpy',
-        'pandas',
-        'petab',
-        'pyyaml',
-        #'python-libsbml>=5.17.0',
-        #'sympy',
-        # required for CLI
-        'click',
-        'dill',
-        # plotting
-        #'matplotlib>=2.2.3',
-        #'seaborn',
-    ],
+    url=f"https://github.com/{org}/{repo}",
+    packages=find_packages(exclude=["doc*", "test*"]),
     include_package_data=True,
-    python_requires=f'>={minimum_python_version}',
-    entry_points=ENTRY_POINTS,
-    extras_require={
-        'test': [
-            'pytest >= 5.4.3',
-            'pytest-cov >= 2.10.0',
-            'amici >= 0.11.25',
-            'fides >= 0.7.5',
-            # FIXME
-            'pypesto > 0.2.13',
-            # 'pypesto @ git+https://github.com/ICB-DCM/pyPESTO.git@develop#egg=pypesto',
-        ],
-        'doc': [
-            'sphinx>=3.5.3,<7',
-            'sphinxcontrib-napoleon>=0.7',
-            'sphinx-markdown-tables>=0.0.15',
-            'sphinx-rtd-theme>=0.5.1',
-            'recommonmark>=0.7.1',
-            # pin until ubuntu comes with newer pandoc:
-            # /home/docs/checkouts/readthedocs.org/user_builds/petab-select/envs/63/lib/python3.11/site-packages/nbsphinx/__init__.py:1058: RuntimeWarning: You are using an unsupported version of pandoc (2.9.2.1).
-            # Your version must be at least (2.14.2) but less than (4.0.0).
-            'nbsphinx==0.9.1',
-            'nbconvert<7.5.0',
-            'ipython>=7.21.0',
-            'readthedocs-sphinx-ext>=2.2.5',
-            'sphinx-autodoc-typehints',
-        ],
-    },
 )
diff --git a/tox.ini b/tox.ini
index 264971a9..139c8b0d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -18,23 +18,10 @@ description =
 
 [testenv:base]
 extras = test
+deps =
+    git+https://github.com/ICB-DCM/pyPESTO.git@develop\#egg=pypesto
 commands =
   pytest --cov=petab_select --cov-report=xml --cov-append test -s
   coverage report
 description =
   Test basic functionality
-
-[testenv:flake8]
-skip_install = true
-deps =
-    flake8 >= 3.8.3
-    flake8-bandit >= 2.1.2
-    flake8-bugbear >= 20.1.4
-    flake8-colors >= 0.1.6
-    flake8-comprehensions >= 3.2.3
-    flake8-print >= 3.1.4
-    flake8-docstrings >= 1.6.0
-commands =
-    flake8 petab_select test setup.py
-description =
-    Run flake8 with various plugins.


Test patch:
diff --git a/doc/test_suite.rst b/doc/test_suite.rst
new file mode 100644
index 00000000..9b9aa443
--- /dev/null
+++ b/doc/test_suite.rst
@@ -0,0 +1,88 @@
+Model selection test suite
+==========================
+
+Several test cases are provided, to test the compatibility of a
+PEtab-compatible calibration tool with different PEtab Select features.
+
+The test cases are available in the ``test_cases`` directory, and are provided in
+the model format.
+
+.. list-table::
+   :header-rows: 1
+
+   * - Test ID
+     - Criterion
+     - Method
+     - Model space files
+     - Compressed format
+     - Constraints files
+     - Predecessor (initial) models files
+   * - 0001
+     - (all)
+     - (only one model)
+     - 1
+     -
+     -
+     -
+   * - 0002 [#f1]_
+     - AIC
+     - forward
+     - 1
+     -
+     -
+     -
+   * - 0003
+     - BIC
+     - all
+     - 1
+     - Yes
+     -
+     -
+   * - 0004
+     - AICc
+     - backward
+     - 1
+     -
+     - 1
+     -
+   * - 0005
+     - AIC
+     - forward
+     - 1
+     -
+     -
+     - 1
+   * - 0006
+     - AIC
+     - forward
+     - 1
+     -
+     -
+     -
+   * - 0007 [#f2]_
+     - AIC
+     - forward
+     - 1
+     -
+     -
+     -
+   * - 0008 [#f2]_
+     - AICc
+     - backward
+     - 1
+     -
+     -
+     -
+   * - 0009 [#f3]_
+     - AICc
+     - FAMoS
+     - 1
+     - Yes
+     -
+     - Yes
+
+.. [#f1] Model ``M1_0`` differs from ``M1_1`` in three parameters, but only 1 additional estimated parameter. The effect of this on model selection criteria needs to be clarified. Test case 0006 is a duplicate of 0002 that doesn't have this issue.
+
+.. [#f2] Noise parameter is removed, noise is fixed to ``1``.
+
+.. [#f3] This is a computationally expensive problem to solve. Developers can try a model selection initialized with the provided predecessor model, which is a model start that reproducibly finds the expected model. To solve the problem reproducibly *ab initio*, on the order of 100 random model starts are required. This test case reproduces the model selection problem presented in https://doi.org/10.1016/j.cels.2016.01.002.
diff --git a/test/candidate_space/input/famos_synthetic/test_files/calibration_results.tsv b/test/candidate_space/input/famos_synthetic/test_files/calibration_results.tsv
index 4a6c0702..cc475121 100644
--- a/test/candidate_space/input/famos_synthetic/test_files/calibration_results.tsv
+++ b/test/candidate_space/input/famos_synthetic/test_files/calibration_results.tsv
@@ -1,282 +1,282 @@
-model_id	AICc
-M_0000010010011011	139213.93785488105
-M_0000011010011010	147241.83250045744
-M_0000110010001011	129085.62527164364
-M_0001000011010011	20019.422755830317
-M_0001000110010011	20019.42287885885
-M_0001001011010010	58645.5159924183
-M_0001001110010010	85944.52663282724
-M_0001010000011011	561253.8821574149
-M_0001010010000111	17407.944853575715
-M_0001010010001101	11373.70265679193
-M_0001010010010011	20019.42291165012
-M_0001010010011001	19822.224390562587
-M_0001010010110010	223159.7600795708
-M_0001011000011010	119758.91035527596
-M_0001011010000110	52539.03132570987
-M_0001011010010010	85944.5264701275
-M_0001011010011000	85944.52656271843
-M_0001100011000011	8783.477653560316
-M_0001100011001001	2082.2848460706477
-M_0001100110000011	8783.477689063757
-M_0001100110001001	2122.93902388184
-M_0001110000001011	2126.584221871469
-M_0001110010000011	5989.975092399021
-M_0001110010001001	2122.9390944314687
-M_0001110010100010	66865.97525944727
-M_0001110010101000	66865.97526955212
-M_0001111010000010	81844.29973880656
-M_0001111010001000	81844.29970386633
-M_0010100000101000	13964.86529487194
-M_0010100000101100	13967.043858068218
-M_0010100000111000	4668.469165871618
-M_0011010010010010	223159.76006183933
-M_0011100000101000	2745.410237113948
-M_0011100000101001	1245.2636262725478
-M_0011110010000010	209279.1634871877
-M_0011110010001000	206824.56340461335
-M_0100100000101000	23409.18328914282
-M_0100100000101001	10168.445671589136
-M_0100101000101000	23411.36177928765
-M_0101010010000011	21480.37645074097
-M_0101010010001001	21480.37627690475
-M_0101011010000010	85944.52650071045
-M_0101100000101000	22297.504133595157
-M_0101100000101001	1828.464353816787
-M_0110000000101000	561249.48662067
-M_0110000000101100	561251.6651103725
-M_0110000000111000	561251.6651103725
-M_0110100000001000	206221.04167048345
-M_0110100000001001	2806.047402348072
-M_0110100000100000	177910.00743008128
-M_0110100000100010	33340.41218209771
-M_0110100000101000	232.113265665936
-M_0110100000101001	234.3303138148792
-M_0110100000101010	234.3156308348245
-M_0110100000101100	234.33031342028949
-M_0110100000111000	234.3268229275457
-M_0110100001101000	234.33031314833016
-M_0110100001101010	236.56805265000804
-M_0110100010100000	15756.946297002763
-M_0110100010101000	234.33031288019092
-M_0110100011101000	236.5869504485113
-M_0110100100101000	234.3273835278236
-M_0110100100101010	236.5691109524262
-M_0110100110101000	236.5840203029052
-M_0110101000001000	497.3707527496445
-M_0110101000101000	234.33031295737
-M_0110110000101000	234.32946884457883
-M_0110110000101010	236.5687357954883
-M_0110110010101000	236.58610604593983
-M_0111000000101000	561251.6651103725
-M_0111000000101001	561253.8821574149
-M_0111100000001000	206223.21384086332
-M_0111100000001001	606.9037822865472
-M_0111100000100000	177912.18591824788
-M_0111100000100001	177914.40296609284
-M_0111100000101000	234.3250077924832
-M_0111100000101001	236.54705233061625
-M_0111100000101101	238.84432917693465
-M_0111100010101000	236.5816448113885
-M_0111100010101001	238.84434958475077
-M_0111100100101101	241.1790156115896
-M_0111100101101101	243.56100083729265
-M_0111101010101001	241.18341611694956
-M_0111110101101101	245.98625534924483
-M_1000100010001011	25488.987549211623
-M_1000110111010111	33682.94076210533
-M_1001000010001101	10059.448865593136
-M_1001000010010011	14983.46222088898
-M_1001000010011001	14983.462223649214
-M_1001001010010010	62368.876932390696
-M_1001100000001011	1876.108100244288
-M_1001100010000011	7554.901601882608
-M_1001100010001001	1876.108025959749
-M_1001100010001011	1878.3646939136331
-M_1001100010001101	1874.3052802249747
-M_1001100010011001	1878.3647690190437
-M_1001100010101000	29004.845018270666
-M_1001100010101001	1878.364725287871
-M_1001100011001001	1878.364628524238
-M_1001100110001001	1878.364779043418
-M_1001101010001000	48417.47097786767
-M_1001101010001001	1878.3645949487295
-M_1001110010001001	1878.3647795773736
-M_1001110101011101	1881.3236370601333
-M_1001110111010101	5512.646485215627
-M_1010100000101010	554.6582992812749
-M_1010100000101110	506.043663839271
-M_1010100000111010	497.6509421175728
-M_1010100010101000	882.8702477394546
-M_1010100010101100	885.1269082847127
-M_1010100010111000	601.7940759062141
-M_1011100000101001	433.3882550546375
-M_1011100000101101	360.01073998316195
-M_1011100010001000	206269.22383117743
-M_1011100010001001	572.4115972853955
-M_1011100010001011	574.7089604363986
-M_1011100010001101	574.2624322321558
-M_1011100010011001	574.7073976582066
-M_1011100010101000	405.8386721143808
-M_1011100010101001	382.5778905589666
-M_1011100011001001	574.7089621871352
-M_1011100100101101	362.3498014779889
-M_1011100101101101	364.7317910137515
-M_1011100110001001	574.7089926825686
-M_1011101010001001	301.201692408097
-M_1011101010001011	303.5407915622274
-M_1011101010001101	295.47824301127844
-M_1011101010011001	301.52029337677715
-M_1011101010101001	303.54077561640514
-M_1011101011001001	303.5407901163882
-M_1011101110001001	303.54079317899004
-M_1011110010001001	574.7089840081791
-M_1011110101101101	367.15788201970594
-M_1011111010001001	303.54077122448376
-M_1100010111010111	49813.91784706038
-M_1100100000101010	17877.442288126847
-M_1100100000101011	4770.4712415596505
-M_1100100010101000	9773.734269092403
-M_1100100010101001	4770.471310151174
-M_1100101000101010	17879.698953110583
-M_1100101010101000	9775.990866941636
-M_1100110101001111	4777.489639680295
-M_1100110111000111	30332.80096997364
-M_1100110111001101	4777.489716081316
-M_1100110111010011	30332.800929159228
-M_1100110111100110	11016.328616609726
-M_1100111111000110	41228.25894861741
-M_1101000010001001	16723.60491947643
-M_1101010101011101	397001.17155116936
-M_1101010111010101	10043.947785511777
-M_1101100000101001	1828.4669039830949
-M_1101100000101101	1830.7642054619791
-M_1101100010001001	1828.466866432956
-M_1101100010101000	9512.205257685406
-M_1101100010101001	1830.764168943262
-M_1101100100101101	1833.1032679486905
-M_1101100101101101	1835.4852513465949
-M_1101101010101001	1833.1032581612865
-M_1101110101000111	383914.7604399954
-M_1101110101001101	1835.4852672346087
-M_1101110101001111	1837.9113687541485
-M_1101110101011001	1835.4852758742516
-M_1101110101011101	1837.9113595304132
-M_1101110101101100	9531.431831583666
-M_1101110101101101	1837.9113687612264
-M_1101110111000101	3646.48444481816
-M_1101110111001101	1837.9113548340729
-M_1101110111010001	3646.484428093567
-M_1101110111100100	9470.132468078638
-M_1101111101001100	41030.44544078736
-M_1101111101001101	1837.9113562254213
-M_1101111111000100	41226.41791870745
-M_1110000000101010	396991.89656571264
-M_1110000000101110	396994.1532027124
-M_1110000000111010	396994.1532025714
-M_1110000010101000	225250.80565647891
-M_1110000010101100	7354.181781450917
-M_1110000010111000	225253.0622635607
-M_1110100000001010	206225.4249527701
-M_1110100000001011	709.8139285203328
-M_1110100000100010	32569.11777402543
-M_1110100000101000	234.32053581346983
-M_1110100000101001	236.57717236056465
-M_1110100000101010	236.5309219253127
-M_1110100000101011	238.7752196664412
-M_1110100000101101	238.87446942005604
-M_1110100000101110	238.81920116626029
-M_1110100000111010	238.8209933841467
-M_1110100001101010	238.82769297452745
-M_1110100010001000	206225.42523656492
-M_1110100010001001	1903.1635775769812
-M_1110100010100000	6175.236251468945
-M_1110100010100010	2435.7230027787205
-M_1110100010101000	236.57717283177325
-M_1110100010101001	238.87447010638908
-M_1110100010101010	238.82096515868363
-M_1110100010101100	238.8744691260788
-M_1110100010111000	238.85880783638197
-M_1110100011101000	238.87446936299148
-M_1110100100101010	238.8271393711566
-M_1110100100101101	241.2116011501129
-M_1110100101101101	243.593585788594
-M_1110100110101000	238.87253451641828
-M_1110101000001010	381.953412082261
-M_1110101000101010	238.82821959285397
-M_1110101010001000	382.0059312235692
-M_1110101010101000	238.87446999761505
-M_1110101010101001	241.21353703929177
-M_1110110000101010	238.82821945073889
-M_1110110010101000	238.8744704850313
-M_1110110101101101	246.0196818158347
-M_1110110111000110	211557.76697941415
-M_1111000000101001	396994.1532026271
-M_1111000000101101	396996.4504998744
-M_1111000010101000	220481.7872639834
-M_1111000010101001	14580.346343644816
-M_1111000100101101	396998.78956616647
-M_1111000101101101	397001.1715515276
-M_1111001010101001	14505.882802401526
-M_1111010101101101	397003.59764623776
-M_1111100000001001	606.9905832634503
-M_1111100000001101	602.4411335548343
-M_1111100000100001	177916.64191094312
-M_1111100000100010	14189.991928330788
-M_1111100000100101	177918.93920798044
-M_1111100000101000	236.5470995198401
-M_1111100000101001	238.80076560298963
-M_1111100000101010	238.8136978955608
-M_1111100000101100	238.8331287054401
-M_1111100000101101	241.1222202761463
-M_1111100001101101	243.50420511898895
-M_1111100010001000	206227.63311331163
-M_1111100010001001	568.9552843715751
-M_1111100010100000	1404.2903801434873
-M_1111100010100001	1406.5384305557384
-M_1111100010101000	238.83160653758773
-M_1111100010101001	241.12867656656744
-M_1111100100001101	604.7802175312609
-M_1111100100100101	177921.2780036785
-M_1111100100101001	241.13748677613373
-M_1111100100101100	241.1720398184164
-M_1111100100101101	243.50405343595625
-M_1111100101001101	607.1622021577575
-M_1111100101100101	177923.66001179285
-M_1111100101101001	243.5194719023592
-M_1111100101101100	243.55402510830623
-M_1111100101101101	245.9301495766389
-M_1111101000101001	241.12880866953333
-M_1111101010001001	261.3039091174704
-M_1111101010001011	263.6116954813844
-M_1111101010001101	263.66190443322904
-M_1111101010011001	263.65972246373093
-M_1111101010100001	1408.87016504161
-M_1111101010101000	241.17067087762751
-M_1111101010101001	243.49951157260497
-M_1111101010101011	245.90866663807364
-M_1111101010101101	245.91762078132183
-M_1111101010111001	245.91680600377367
-M_1111101011001001	263.6858866332959
-M_1111101011101001	245.92560809043647
-M_1111101110001001	263.6849507178238
-M_1111101110101001	245.92424808908
-M_1111110001101101	245.93030121541156
-M_1111110100101101	245.9301495839917
-M_1111110101001100	206234.6454773945
-M_1111110101001101	609.5826436734602
-M_1111110101001111	606.3868920612166
-M_1111110101011101	612.0540839150935
-M_1111110101100101	177926.08608462897
-M_1111110101101001	245.94556766090545
-M_1111110101101100	245.98012071080976
-M_1111110101101101	248.40159324081264
-M_1111110101101111	250.9062566610432
-M_1111110101111101	250.91785846088345
-M_1111110111000100	207402.40135257572
-M_1111110111001101	578.5738952510358
-M_1111110111101101	250.91739945255304
-M_1111111010001001	263.6858967007385
-M_1111111010101001	245.92560787196052
-M_1111111101001101	268.57688618118783
-M_1111111101101101	250.90966149938663
+model_hash	AICc
+model_subspace_1-0000010010011011	139213.93785488105
+model_subspace_1-0000011010011010	147241.83250045744
+model_subspace_1-0000110010001011	129085.62527164364
+model_subspace_1-0001000011010011	20019.422755830317
+model_subspace_1-0001000110010011	20019.42287885885
+model_subspace_1-0001001011010010	58645.5159924183
+model_subspace_1-0001001110010010	85944.52663282724
+model_subspace_1-0001010000011011	561253.8821574149
+model_subspace_1-0001010010000111	17407.944853575715
+model_subspace_1-0001010010001101	11373.70265679193
+model_subspace_1-0001010010010011	20019.42291165012
+model_subspace_1-0001010010011001	19822.224390562587
+model_subspace_1-0001010010110010	223159.7600795708
+model_subspace_1-0001011000011010	119758.91035527596
+model_subspace_1-0001011010000110	52539.03132570987
+model_subspace_1-0001011010010010	85944.5264701275
+model_subspace_1-0001011010011000	85944.52656271843
+model_subspace_1-0001100011000011	8783.477653560316
+model_subspace_1-0001100011001001	2082.2848460706477
+model_subspace_1-0001100110000011	8783.477689063757
+model_subspace_1-0001100110001001	2122.93902388184
+model_subspace_1-0001110000001011	2126.584221871469
+model_subspace_1-0001110010000011	5989.975092399021
+model_subspace_1-0001110010001001	2122.9390944314687
+model_subspace_1-0001110010100010	66865.97525944727
+model_subspace_1-0001110010101000	66865.97526955212
+model_subspace_1-0001111010000010	81844.29973880656
+model_subspace_1-0001111010001000	81844.29970386633
+model_subspace_1-0010100000101000	13964.86529487194
+model_subspace_1-0010100000101100	13967.043858068218
+model_subspace_1-0010100000111000	4668.469165871618
+model_subspace_1-0011010010010010	223159.76006183933
+model_subspace_1-0011100000101000	2745.410237113948
+model_subspace_1-0011100000101001	1245.2636262725478
+model_subspace_1-0011110010000010	209279.1634871877
+model_subspace_1-0011110010001000	206824.56340461335
+model_subspace_1-0100100000101000	23409.18328914282
+model_subspace_1-0100100000101001	10168.445671589136
+model_subspace_1-0100101000101000	23411.36177928765
+model_subspace_1-0101010010000011	21480.37645074097
+model_subspace_1-0101010010001001	21480.37627690475
+model_subspace_1-0101011010000010	85944.52650071045
+model_subspace_1-0101100000101000	22297.504133595157
+model_subspace_1-0101100000101001	1828.464353816787
+model_subspace_1-0110000000101000	561249.48662067
+model_subspace_1-0110000000101100	561251.6651103725
+model_subspace_1-0110000000111000	561251.6651103725
+model_subspace_1-0110100000001000	206221.04167048345
+model_subspace_1-0110100000001001	2806.047402348072
+model_subspace_1-0110100000100000	177910.00743008128
+model_subspace_1-0110100000100010	33340.41218209771
+model_subspace_1-0110100000101000	232.113265665936
+model_subspace_1-0110100000101001	234.3303138148792
+model_subspace_1-0110100000101010	234.3156308348245
+model_subspace_1-0110100000101100	234.33031342028949
+model_subspace_1-0110100000111000	234.3268229275457
+model_subspace_1-0110100001101000	234.33031314833016
+model_subspace_1-0110100001101010	236.56805265000804
+model_subspace_1-0110100010100000	15756.946297002763
+model_subspace_1-0110100010101000	234.33031288019092
+model_subspace_1-0110100011101000	236.5869504485113
+model_subspace_1-0110100100101000	234.3273835278236
+model_subspace_1-0110100100101010	236.5691109524262
+model_subspace_1-0110100110101000	236.5840203029052
+model_subspace_1-0110101000001000	497.3707527496445
+model_subspace_1-0110101000101000	234.33031295737
+model_subspace_1-0110110000101000	234.32946884457883
+model_subspace_1-0110110000101010	236.5687357954883
+model_subspace_1-0110110010101000	236.58610604593983
+model_subspace_1-0111000000101000	561251.6651103725
+model_subspace_1-0111000000101001	561253.8821574149
+model_subspace_1-0111100000001000	206223.21384086332
+model_subspace_1-0111100000001001	606.9037822865472
+model_subspace_1-0111100000100000	177912.18591824788
+model_subspace_1-0111100000100001	177914.40296609284
+model_subspace_1-0111100000101000	234.3250077924832
+model_subspace_1-0111100000101001	236.54705233061625
+model_subspace_1-0111100000101101	238.84432917693465
+model_subspace_1-0111100010101000	236.5816448113885
+model_subspace_1-0111100010101001	238.84434958475077
+model_subspace_1-0111100100101101	241.1790156115896
+model_subspace_1-0111100101101101	243.56100083729265
+model_subspace_1-0111101010101001	241.18341611694956
+model_subspace_1-0111110101101101	245.98625534924483
+model_subspace_1-1000100010001011	25488.987549211623
+model_subspace_1-1000110111010111	33682.94076210533
+model_subspace_1-1001000010001101	10059.448865593136
+model_subspace_1-1001000010010011	14983.46222088898
+model_subspace_1-1001000010011001	14983.462223649214
+model_subspace_1-1001001010010010	62368.876932390696
+model_subspace_1-1001100000001011	1876.108100244288
+model_subspace_1-1001100010000011	7554.901601882608
+model_subspace_1-1001100010001001	1876.108025959749
+model_subspace_1-1001100010001011	1878.3646939136331
+model_subspace_1-1001100010001101	1874.3052802249747
+model_subspace_1-1001100010011001	1878.3647690190437
+model_subspace_1-1001100010101000	29004.845018270666
+model_subspace_1-1001100010101001	1878.364725287871
+model_subspace_1-1001100011001001	1878.364628524238
+model_subspace_1-1001100110001001	1878.364779043418
+model_subspace_1-1001101010001000	48417.47097786767
+model_subspace_1-1001101010001001	1878.3645949487295
+model_subspace_1-1001110010001001	1878.3647795773736
+model_subspace_1-1001110101011101	1881.3236370601333
+model_subspace_1-1001110111010101	5512.646485215627
+model_subspace_1-1010100000101010	554.6582992812749
+model_subspace_1-1010100000101110	506.043663839271
+model_subspace_1-1010100000111010	497.6509421175728
+model_subspace_1-1010100010101000	882.8702477394546
+model_subspace_1-1010100010101100	885.1269082847127
+model_subspace_1-1010100010111000	601.7940759062141
+model_subspace_1-1011100000101001	433.3882550546375
+model_subspace_1-1011100000101101	360.01073998316195
+model_subspace_1-1011100010001000	206269.22383117743
+model_subspace_1-1011100010001001	572.4115972853955
+model_subspace_1-1011100010001011	574.7089604363986
+model_subspace_1-1011100010001101	574.2624322321558
+model_subspace_1-1011100010011001	574.7073976582066
+model_subspace_1-1011100010101000	405.8386721143808
+model_subspace_1-1011100010101001	382.5778905589666
+model_subspace_1-1011100011001001	574.7089621871352
+model_subspace_1-1011100100101101	362.3498014779889
+model_subspace_1-1011100101101101	364.7317910137515
+model_subspace_1-1011100110001001	574.7089926825686
+model_subspace_1-1011101010001001	301.201692408097
+model_subspace_1-1011101010001011	303.5407915622274
+model_subspace_1-1011101010001101	295.47824301127844
+model_subspace_1-1011101010011001	301.52029337677715
+model_subspace_1-1011101010101001	303.54077561640514
+model_subspace_1-1011101011001001	303.5407901163882
+model_subspace_1-1011101110001001	303.54079317899004
+model_subspace_1-1011110010001001	574.7089840081791
+model_subspace_1-1011110101101101	367.15788201970594
+model_subspace_1-1011111010001001	303.54077122448376
+model_subspace_1-1100010111010111	49813.91784706038
+model_subspace_1-1100100000101010	17877.442288126847
+model_subspace_1-1100100000101011	4770.4712415596505
+model_subspace_1-1100100010101000	9773.734269092403
+model_subspace_1-1100100010101001	4770.471310151174
+model_subspace_1-1100101000101010	17879.698953110583
+model_subspace_1-1100101010101000	9775.990866941636
+model_subspace_1-1100110101001111	4777.489639680295
+model_subspace_1-1100110111000111	30332.80096997364
+model_subspace_1-1100110111001101	4777.489716081316
+model_subspace_1-1100110111010011	30332.800929159228
+model_subspace_1-1100110111100110	11016.328616609726
+model_subspace_1-1100111111000110	41228.25894861741
+model_subspace_1-1101000010001001	16723.60491947643
+model_subspace_1-1101010101011101	397001.17155116936
+model_subspace_1-1101010111010101	10043.947785511777
+model_subspace_1-1101100000101001	1828.4669039830949
+model_subspace_1-1101100000101101	1830.7642054619791
+model_subspace_1-1101100010001001	1828.466866432956
+model_subspace_1-1101100010101000	9512.205257685406
+model_subspace_1-1101100010101001	1830.764168943262
+model_subspace_1-1101100100101101	1833.1032679486905
+model_subspace_1-1101100101101101	1835.4852513465949
+model_subspace_1-1101101010101001	1833.1032581612865
+model_subspace_1-1101110101000111	383914.7604399954
+model_subspace_1-1101110101001101	1835.4852672346087
+model_subspace_1-1101110101001111	1837.9113687541485
+model_subspace_1-1101110101011001	1835.4852758742516
+model_subspace_1-1101110101011101	1837.9113595304132
+model_subspace_1-1101110101101100	9531.431831583666
+model_subspace_1-1101110101101101	1837.9113687612264
+model_subspace_1-1101110111000101	3646.48444481816
+model_subspace_1-1101110111001101	1837.9113548340729
+model_subspace_1-1101110111010001	3646.484428093567
+model_subspace_1-1101110111100100	9470.132468078638
+model_subspace_1-1101111101001100	41030.44544078736
+model_subspace_1-1101111101001101	1837.9113562254213
+model_subspace_1-1101111111000100	41226.41791870745
+model_subspace_1-1110000000101010	396991.89656571264
+model_subspace_1-1110000000101110	396994.1532027124
+model_subspace_1-1110000000111010	396994.1532025714
+model_subspace_1-1110000010101000	225250.80565647891
+model_subspace_1-1110000010101100	7354.181781450917
+model_subspace_1-1110000010111000	225253.0622635607
+model_subspace_1-1110100000001010	206225.4249527701
+model_subspace_1-1110100000001011	709.8139285203328
+model_subspace_1-1110100000100010	32569.11777402543
+model_subspace_1-1110100000101000	234.32053581346983
+model_subspace_1-1110100000101001	236.57717236056465
+model_subspace_1-1110100000101010	236.5309219253127
+model_subspace_1-1110100000101011	238.7752196664412
+model_subspace_1-1110100000101101	238.87446942005604
+model_subspace_1-1110100000101110	238.81920116626029
+model_subspace_1-1110100000111010	238.8209933841467
+model_subspace_1-1110100001101010	238.82769297452745
+model_subspace_1-1110100010001000	206225.42523656492
+model_subspace_1-1110100010001001	1903.1635775769812
+model_subspace_1-1110100010100000	6175.236251468945
+model_subspace_1-1110100010100010	2435.7230027787205
+model_subspace_1-1110100010101000	236.57717283177325
+model_subspace_1-1110100010101001	238.87447010638908
+model_subspace_1-1110100010101010	238.82096515868363
+model_subspace_1-1110100010101100	238.8744691260788
+model_subspace_1-1110100010111000	238.85880783638197
+model_subspace_1-1110100011101000	238.87446936299148
+model_subspace_1-1110100100101010	238.8271393711566
+model_subspace_1-1110100100101101	241.2116011501129
+model_subspace_1-1110100101101101	243.593585788594
+model_subspace_1-1110100110101000	238.87253451641828
+model_subspace_1-1110101000001010	381.953412082261
+model_subspace_1-1110101000101010	238.82821959285397
+model_subspace_1-1110101010001000	382.0059312235692
+model_subspace_1-1110101010101000	238.87446999761505
+model_subspace_1-1110101010101001	241.21353703929177
+model_subspace_1-1110110000101010	238.82821945073889
+model_subspace_1-1110110010101000	238.8744704850313
+model_subspace_1-1110110101101101	246.0196818158347
+model_subspace_1-1110110111000110	211557.76697941415
+model_subspace_1-1111000000101001	396994.1532026271
+model_subspace_1-1111000000101101	396996.4504998744
+model_subspace_1-1111000010101000	220481.7872639834
+model_subspace_1-1111000010101001	14580.346343644816
+model_subspace_1-1111000100101101	396998.78956616647
+model_subspace_1-1111000101101101	397001.1715515276
+model_subspace_1-1111001010101001	14505.882802401526
+model_subspace_1-1111010101101101	397003.59764623776
+model_subspace_1-1111100000001001	606.9905832634503
+model_subspace_1-1111100000001101	602.4411335548343
+model_subspace_1-1111100000100001	177916.64191094312
+model_subspace_1-1111100000100010	14189.991928330788
+model_subspace_1-1111100000100101	177918.93920798044
+model_subspace_1-1111100000101000	236.5470995198401
+model_subspace_1-1111100000101001	238.80076560298963
+model_subspace_1-1111100000101010	238.8136978955608
+model_subspace_1-1111100000101100	238.8331287054401
+model_subspace_1-1111100000101101	241.1222202761463
+model_subspace_1-1111100001101101	243.50420511898895
+model_subspace_1-1111100010001000	206227.63311331163
+model_subspace_1-1111100010001001	568.9552843715751
+model_subspace_1-1111100010100000	1404.2903801434873
+model_subspace_1-1111100010100001	1406.5384305557384
+model_subspace_1-1111100010101000	238.83160653758773
+model_subspace_1-1111100010101001	241.12867656656744
+model_subspace_1-1111100100001101	604.7802175312609
+model_subspace_1-1111100100100101	177921.2780036785
+model_subspace_1-1111100100101001	241.13748677613373
+model_subspace_1-1111100100101100	241.1720398184164
+model_subspace_1-1111100100101101	243.50405343595625
+model_subspace_1-1111100101001101	607.1622021577575
+model_subspace_1-1111100101100101	177923.66001179285
+model_subspace_1-1111100101101001	243.5194719023592
+model_subspace_1-1111100101101100	243.55402510830623
+model_subspace_1-1111100101101101	245.9301495766389
+model_subspace_1-1111101000101001	241.12880866953333
+model_subspace_1-1111101010001001	261.3039091174704
+model_subspace_1-1111101010001011	263.6116954813844
+model_subspace_1-1111101010001101	263.66190443322904
+model_subspace_1-1111101010011001	263.65972246373093
+model_subspace_1-1111101010100001	1408.87016504161
+model_subspace_1-1111101010101000	241.17067087762751
+model_subspace_1-1111101010101001	243.49951157260497
+model_subspace_1-1111101010101011	245.90866663807364
+model_subspace_1-1111101010101101	245.91762078132183
+model_subspace_1-1111101010111001	245.91680600377367
+model_subspace_1-1111101011001001	263.6858866332959
+model_subspace_1-1111101011101001	245.92560809043647
+model_subspace_1-1111101110001001	263.6849507178238
+model_subspace_1-1111101110101001	245.92424808908
+model_subspace_1-1111110001101101	245.93030121541156
+model_subspace_1-1111110100101101	245.9301495839917
+model_subspace_1-1111110101001100	206234.6454773945
+model_subspace_1-1111110101001101	609.5826436734602
+model_subspace_1-1111110101001111	606.3868920612166
+model_subspace_1-1111110101011101	612.0540839150935
+model_subspace_1-1111110101100101	177926.08608462897
+model_subspace_1-1111110101101001	245.94556766090545
+model_subspace_1-1111110101101100	245.98012071080976
+model_subspace_1-1111110101101101	248.40159324081264
+model_subspace_1-1111110101101111	250.9062566610432
+model_subspace_1-1111110101111101	250.91785846088345
+model_subspace_1-1111110111000100	207402.40135257572
+model_subspace_1-1111110111001101	578.5738952510358
+model_subspace_1-1111110111101101	250.91739945255304
+model_subspace_1-1111111010001001	263.6858967007385
+model_subspace_1-1111111010101001	245.92560787196052
+model_subspace_1-1111111101001101	268.57688618118783
+model_subspace_1-1111111101101101	250.90966149938663
diff --git a/test/candidate_space/input/famos_synthetic/test_files/regenerate_model_hashes.py b/test/candidate_space/input/famos_synthetic/test_files/regenerate_model_hashes.py
new file mode 100644
index 00000000..b3b9e37c
--- /dev/null
+++ b/test/candidate_space/input/famos_synthetic/test_files/regenerate_model_hashes.py
@@ -0,0 +1,29 @@
+from pathlib import Path
+
+import pandas as pd
+
+import petab_select
+
+script_dir = Path(__file__).parent.resolve()
+
+petab_select_problem = petab_select.Problem.from_yaml(
+    script_dir / "../select/FAMoS_2019_petab_select_problem.yaml"
+)
+df = pd.read_csv(script_dir / "calibration_results.tsv", sep="\t", dtype=str)
+
+model_hashes = list(df.model_hash)
+new_model_hashes = [
+    petab_select_problem.get_model(
+        model_subspace_id="model_subspace_1",
+        model_subspace_indices=[
+            int(s)
+            for s in petab_select.model.ModelHash.from_hash(
+                model_hash
+            ).unhash_model_subspace_indices()
+        ],
+    ).get_hash()
+    for model_hash in model_hashes
+]
+
+df["model_hash"] = new_model_hashes
+df.to_csv(script_dir / "calibration_results.tsv", sep="\t", index=False)
diff --git a/test/candidate_space/test_candidate_space.py b/test/candidate_space/test_candidate_space.py
index 523f42d7..970a3e00 100644
--- a/test/candidate_space/test_candidate_space.py
+++ b/test/candidate_space/test_candidate_space.py
@@ -2,9 +2,6 @@
 
 import pandas as pd
 import pytest
-from more_itertools import one
-
-import petab_select
 
 # from petab_select.candidate_space import (
 #    BackwardCandidateSpace,
@@ -14,39 +11,32 @@
 # )
 from petab_select.constants import (
     ESTIMATE,
-    MODEL_SUBSPACE_ID,
-    MODELS,
-    PARAMETER_VALUE_DELIMITER,
-    PETAB_YAML,
-    Criterion,
 )
-from petab_select.model import Model, default_compare
 from petab_select.model_space import ModelSpace, get_model_space_df
-from petab_select.model_subspace import ModelSubspace
 
 
 @pytest.fixture
 def ordered_model_parameterizations():
     good_models_ascending = [
         # forward
-        '00000',
-        '10000',
-        '11000',
-        '11100',
-        '11110',
+        "00000",
+        "10000",
+        "11000",
+        "11100",
+        "11110",
         # backward
-        '01110',
-        '01100',
+        "01110",
+        "01100",
         # forward
-        '01101',
-        '01111',
+        "01101",
+        "01111",
         # backward
-        '00111',
-        '00011',
+        "00111",
+        "00011",
     ]
     bad_models = [
-        '01011',
-        '11011',
+        "01011",
+        "11011",
     ]
 
     # All good models are unique
@@ -94,14 +84,14 @@ def model_space(calibrated_model_space) -> pd.DataFrame:
         data["model_subspace_id"].append(f"model_subspace_{model}")
         data["petab_yaml"].append(
             Path(__file__).parent.parent.parent
-            / 'doc'
-            / 'examples'
-            / 'model_selection'
-            / 'petab_problem.yaml'
+            / "doc"
+            / "examples"
+            / "model_selection"
+            / "petab_problem.yaml"
+        )
+        k1, k2, k3, k4, k5 = (
+            "0" if parameter == "0" else ESTIMATE for parameter in model
         )
-        k1, k2, k3, k4, k5 = [
-            '0' if parameter == '0' else ESTIMATE for parameter in model
-        ]
         data["k1"].append(k1)
         data["k2"].append(k2)
         data["k3"].append(k3)
diff --git a/test/candidate_space/test_famos.py b/test/candidate_space/test_famos.py
index 3e1efb37..e7cf12e7 100644
--- a/test/candidate_space/test_famos.py
+++ b/test/candidate_space/test_famos.py
@@ -1,16 +1,19 @@
 from pathlib import Path
-from typing import Tuple
 
-import numpy as np
 import pandas as pd
-import petab
 import pytest
 from more_itertools import one
 
 import petab_select
-from petab_select import ESTIMATE, FamosCandidateSpace, Method, Model
-from petab_select.constants import Criterion
-from petab_select.model import default_compare
+from petab_select import Method
+from petab_select.constants import (
+    CANDIDATE_SPACE,
+    MODEL_HASH,
+    MODELS,
+    TERMINATE,
+    UNCALIBRATED_MODELS,
+    Criterion,
+)
 
 
 @pytest.fixture
@@ -30,8 +33,11 @@ def expected_criterion_values(input_path):
     calibration_results = pd.read_csv(
         input_path / "test_files" / "calibration_results.tsv",
         sep="\t",
-    ).set_index('model_id')
-    return dict(calibration_results['AICc'])
+    ).set_index(MODEL_HASH)
+    return {
+        petab_select.model.ModelHash.from_hash(k): v
+        for k, v in calibration_results[Criterion.AICC].items()
+    }
 
 
 @pytest.fixture
@@ -76,35 +82,29 @@ def expected_progress_list():
     ]
 
 
-@pytest.mark.skip(reason="FIXME")
 def test_famos(
     petab_select_problem,
     expected_criterion_values,
     expected_progress_list,
 ):
-    def set_model_id(model):
-        model.model_id = "M_" + ''.join(
-            '1' if v == ESTIMATE else '0' for v in model.parameters.values()
-        )
-
     def calibrate(
         model,
         expected_criterion_values=expected_criterion_values,
     ) -> None:
         model.set_criterion(
             criterion=petab_select_problem.criterion,
-            value=expected_criterion_values[model.model_id],
+            value=expected_criterion_values[model.get_hash()],
         )
 
-    def parse_summary_to_progress_list(summary_tsv: str) -> Tuple[Method, set]:
+    def parse_summary_to_progress_list(summary_tsv: str) -> tuple[Method, set]:
         """Get progress information from the summary file."""
-        df_raw = pd.read_csv(summary_tsv, sep='\t')
+        df_raw = pd.read_csv(summary_tsv, sep="\t")
         df = df_raw.loc[~pd.isnull(df_raw["predecessor change"])]
 
         parameter_list = list(
-            petab_select_problem.model_space.model_subspaces[
-                'model_subspace_1'
-            ].parameters
+            one(
+                petab_select_problem.model_space.model_subspaces.values()
+            ).parameters
         )
 
         progress_list = []
@@ -126,43 +126,52 @@ def parse_summary_to_progress_list(summary_tsv: str) -> Tuple[Method, set]:
         return progress_list
 
     progress_list = []
+    all_calibrated_models = {}
     calibrated_models = {}
-    newly_calibrated_models = {}
 
     candidate_space = petab_select_problem.new_candidate_space()
     candidate_space.summary_tsv.unlink(missing_ok=True)
     candidate_space._setup_summary_tsv()
 
-    with pytest.raises(StopIteration, match="No valid models found."):
-        predecessor_model = candidate_space.predecessor_model
+    with (
+        pytest.raises(StopIteration, match="No valid models found."),
+        pytest.warns(RuntimeWarning) as warning_record,
+    ):
         while True:
-            # Save predecessor_models and find new candidates
-            if candidate_space.predecessor_model is not None:
-                previous_predecessor_model = candidate_space.predecessor_model
-            else:
-                previous_predecessor_model = one(candidate_space.models)
-            candidate_space = petab_select.ui.candidates(
+            # Initialize iteration
+            iteration = petab_select.ui.start_iteration(
                 problem=petab_select_problem,
                 candidate_space=candidate_space,
-                calibrated_models=calibrated_models,
-                newly_calibrated_models=newly_calibrated_models,
-                previous_predecessor_model=previous_predecessor_model,
             )
 
             # Calibrate candidate models
-            newly_calibrated_models = {}
-            for candidate_model in candidate_space.models:
-                set_model_id(candidate_model)
+            calibrated_models = {}
+            for candidate_model in iteration[UNCALIBRATED_MODELS]:
                 calibrate(candidate_model)
-                newly_calibrated_models[
-                    candidate_model.get_hash()
-                ] = candidate_model
-                calibrated_models.update(newly_calibrated_models)
+                calibrated_models[candidate_model.get_hash()] = candidate_model
+
+            # Finalize iteration
+            iteration_results = petab_select.ui.end_iteration(
+                candidate_space=iteration[CANDIDATE_SPACE],
+                calibrated_models=calibrated_models,
+            )
+            all_calibrated_models.update(iteration_results[MODELS])
+            candidate_space = iteration_results[CANDIDATE_SPACE]
 
             # Stop iteration if there are no candidate models
-            if not candidate_space.models:
+            if iteration_results[TERMINATE]:
                 raise StopIteration("No valid models found.")
 
+    # A model is encountered twice and therefore skipped.
+    expected_repeated_model_hash = petab_select_problem.get_model(
+        model_subspace_id=one(
+            petab_select_problem.model_space.model_subspaces
+        ),
+        model_subspace_indices=[int(s) for s in "0001011010010010"],
+    ).get_hash()
+    assert len(warning_record) == 1
+    assert expected_repeated_model_hash in warning_record[0].message.args[0]
+
     progress_list = parse_summary_to_progress_list(candidate_space.summary_tsv)
 
     assert progress_list == expected_progress_list, progress_list
diff --git a/test/cli/test_cli.py b/test/cli/test_cli.py
index c01982d3..0a4dc34d 100644
--- a/test/cli/test_cli.py
+++ b/test/cli/test_cli.py
@@ -12,22 +12,22 @@
 
 @pytest.fixture
 def output_path() -> Path:
-    return base_dir / 'output'
+    return base_dir / "output"
 
 
 @pytest.fixture
 def expected_output_path() -> Path:
-    return base_dir / 'expected_output'
+    return base_dir / "expected_output"
 
 
 @pytest.fixture
 def model_yaml() -> Path:
-    return base_dir / 'input' / 'model.yaml'
+    return base_dir / "input" / "model.yaml"
 
 
 @pytest.fixture
 def models_yaml() -> Path:
-    return base_dir / 'input' / 'models.yaml'
+    return base_dir / "input" / "models.yaml"
 
 
 @pytest.fixture
@@ -42,15 +42,15 @@ def test_model_to_petab(
     cli_runner,
 ) -> None:
     """Test conversion of a model to PEtab problem files."""
-    output_path_model = output_path / 'model'
+    output_path_model = output_path / "model"
     output_path_model.mkdir(parents=True, exist_ok=True)
 
     result = cli_runner.invoke(
         petab_select.cli.model_to_petab,
         [
-            '--model',
+            "--model",
             model_yaml,
-            '--output',
+            "--output",
             output_path_model,
         ],
     )
@@ -62,18 +62,18 @@ def test_model_to_petab(
     )
 
     comparison = filecmp.dircmp(
-        expected_output_path / 'model',
+        expected_output_path / "model",
         output_path_model,
     )
     # The PEtab problem files are as expected.
     assert not comparison.diff_files
     assert sorted(comparison.same_files) == [
-        'conditions.tsv',
-        'measurements.tsv',
-        'model.xml',
-        'observables.tsv',
-        'parameters.tsv',
-        'problem.yaml',
+        "conditions.tsv",
+        "measurements.tsv",
+        "model.xml",
+        "observables.tsv",
+        "parameters.tsv",
+        "problem.yaml",
     ]
 
 
@@ -84,15 +84,15 @@ def test_models_to_petab(
     cli_runner,
 ) -> None:
     """Test conversion of multiple models to PEtab problem files."""
-    output_path_models = output_path / 'models'
+    output_path_models = output_path / "models"
     output_path_models.mkdir(parents=True, exist_ok=True)
 
     result = cli_runner.invoke(
         petab_select.cli.models_to_petab,
         [
-            '--models',
+            "--models",
             models_yaml,
-            '--output',
+            "--output",
             output_path_models,
         ],
     )
@@ -105,46 +105,46 @@ def test_models_to_petab(
     )
 
     comparison = filecmp.dircmp(
-        expected_output_path / 'models' / 'model_1',
-        output_path_models / 'model_1',
+        expected_output_path / "models" / "model_1",
+        output_path_models / "model_1",
     )
     # The first set of PEtab problem files are as expected.
     assert not comparison.diff_files
     assert sorted(comparison.same_files) == [
-        'conditions.tsv',
-        'measurements.tsv',
-        'model.xml',
-        'observables.tsv',
-        'parameters.tsv',
-        'problem.yaml',
+        "conditions.tsv",
+        "measurements.tsv",
+        "model.xml",
+        "observables.tsv",
+        "parameters.tsv",
+        "problem.yaml",
     ]
 
     comparison = filecmp.dircmp(
-        expected_output_path / 'models' / 'model_2',
-        output_path_models / 'model_2',
+        expected_output_path / "models" / "model_2",
+        output_path_models / "model_2",
     )
     # The second set of PEtab problem files are as expected.
     assert not comparison.diff_files
     assert sorted(comparison.same_files) == [
-        'conditions.tsv',
-        'measurements.tsv',
-        'model.xml',
-        'observables.tsv',
-        'parameters.tsv',
-        'problem.yaml',
+        "conditions.tsv",
+        "measurements.tsv",
+        "model.xml",
+        "observables.tsv",
+        "parameters.tsv",
+        "problem.yaml",
     ]
 
     comparison = filecmp.dircmp(
-        output_path_models / 'model_1',
-        output_path_models / 'model_2',
+        output_path_models / "model_1",
+        output_path_models / "model_2",
     )
     # The first and second set of PEtab problems only differ in their
     # parameters table and nowhere else.
-    assert comparison.diff_files == ['parameters.tsv']
+    assert comparison.diff_files == ["parameters.tsv"]
     assert sorted(comparison.same_files) == [
-        'conditions.tsv',
-        'measurements.tsv',
-        'model.xml',
-        'observables.tsv',
-        'problem.yaml',
+        "conditions.tsv",
+        "measurements.tsv",
+        "model.xml",
+        "observables.tsv",
+        "problem.yaml",
     ]
diff --git a/test/model/test_model.py b/test/model/test_model.py
index 1717b4d2..ffe0956d 100644
--- a/test/model/test_model.py
+++ b/test/model/test_model.py
@@ -10,22 +10,22 @@
 
 @pytest.fixture
 def output_path() -> Path:
-    return base_dir / 'output'
+    return base_dir / "output"
 
 
 @pytest.fixture
 def expected_output_path() -> Path:
-    return base_dir / 'expected_output'
+    return base_dir / "expected_output"
 
 
 @pytest.fixture
 def model() -> Model:
-    return Model.from_yaml(base_dir / 'input' / 'model.yaml')
+    return Model.from_yaml(base_dir / "input" / "model.yaml")
 
 
 def test_model_to_petab(model, output_path, expected_output_path) -> None:
     """Test conversion of a model to a PEtab problem and files."""
-    output_path_petab = output_path / 'petab'
+    output_path_petab = output_path / "petab"
     output_path_petab.mkdir(parents=True, exist_ok=True)
     # TODO test `petab_problem`? Shouldn't be necessary since the generated
     # files are tested below.
@@ -34,16 +34,16 @@ def test_model_to_petab(model, output_path, expected_output_path) -> None:
     )
 
     comparison = filecmp.dircmp(
-        expected_output_path / 'petab',
+        expected_output_path / "petab",
         output_path_petab,
     )
     # The PEtab problem files are as expected.
     assert not comparison.diff_files
     assert sorted(comparison.same_files) == [
-        'conditions.tsv',
-        'measurements.tsv',
-        'model.xml',
-        'observables.tsv',
-        'parameters.tsv',
-        'problem.yaml',
+        "conditions.tsv",
+        "measurements.tsv",
+        "model.xml",
+        "observables.tsv",
+        "parameters.tsv",
+        "problem.yaml",
     ]
diff --git a/test/model_space/test_model_space.py b/test/model_space/test_model_space.py
index de690713..ace5560e 100644
--- a/test/model_space/test_model_space.py
+++ b/test/model_space/test_model_space.py
@@ -1,32 +1,26 @@
 from pathlib import Path
-from typing import List
 
-import pandas as pd
 import pytest
 
 from petab_select.candidate_space import (
     BackwardCandidateSpace,
     BruteForceCandidateSpace,
     ForwardCandidateSpace,
-    LateralCandidateSpace,
 )
 from petab_select.constants import (
     ESTIMATE,
-    MODEL_SUBSPACE_ID,
-    PARAMETER_VALUE_DELIMITER,
-    PETAB_YAML,
+    Criterion,
 )
-from petab_select.model import Model
 from petab_select.model_space import ModelSpace
 
 base_dir = Path(__file__).parent
 
 
 @pytest.fixture
-def model_space_files() -> List[Path]:
+def model_space_files() -> list[Path]:
     return [
-        base_dir / 'model_space_file_1.tsv',
-        base_dir / 'model_space_file_2.tsv',
+        base_dir / "model_space_file_1.tsv",
+        base_dir / "model_space_file_2.tsv",
     ]
 
 
@@ -36,7 +30,7 @@ def model_space(model_space_files) -> ModelSpace:
 
 
 def test_model_space_forward_virtual(model_space):
-    candidate_space = ForwardCandidateSpace()
+    candidate_space = ForwardCandidateSpace(criterion=Criterion.NLLH)
     model_space.search(candidate_space)
 
     # The forward candidate space is initialized without a model, so a virtual initial
@@ -44,16 +38,16 @@ def test_model_space_forward_virtual(model_space):
     # fixed parameters as possible) in the model space.
     expected_models = [
         (
-            'model_subspace_1',
-            {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0.0},
+            "model_subspace_1",
+            {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": 0.0},
         ),
         (
-            'model_subspace_1',
-            {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0.1},
+            "model_subspace_1",
+            {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": 0.1},
         ),
         (
-            'model_subspace_2',
-            {'k1': 0.0, 'k2': 0.0, 'k3': 0.0, 'k4': ESTIMATE},
+            "model_subspace_2",
+            {"k1": 0.0, "k2": 0.0, "k3": 0.0, "k4": ESTIMATE},
         ),
     ]
 
@@ -63,27 +57,30 @@ def test_model_space_forward_virtual(model_space):
     ]
 
     # Search found only expected models.
-    assert all([model in expected_models for model in models])
+    assert all(model in expected_models for model in models)
     # All expected models have now been added to the candidate space.
-    assert all([model in models for model in expected_models])
+    assert all(model in models for model in expected_models)
     # Probably unnecessary: same number of models in expectation vs realization
     assert len(expected_models) == len(candidate_space.models)
 
 
-@pytest.mark.filterwarnings('ignore:Model has been previously excluded')
+@pytest.mark.filterwarnings("ignore:Model has been previously excluded")
 def test_model_space_backward_virtual(model_space):
-    candidate_space = BackwardCandidateSpace()
+    candidate_space = BackwardCandidateSpace(criterion=Criterion.NLLH)
     model_space.search(candidate_space)
 
-    # The forward candidate space is initialized without a model, so a virtual initial
-    # model is used. This means the expected models are the "smallest" models (as many
-    # fixed parameters as possible) in the model space.
+    # The backward candidate space is initialized without a model, so a virtual
+    # initial model is used. This means the expected models are the "smallest"
+    # models (as many fixed parameters as possible) in the model space.
     expected_models = [
-        ('model_subspace_1', {f'k{i}': ESTIMATE for i in range(1, 5)}),
+        ("model_subspace_1", {f"k{i}": ESTIMATE for i in range(1, 5)}),
+        # This model could be excluded, if the hashes/model comparisons enabled
+        # identification of identical models between different subspaces.
+        # TODO delete above, keep below comment, when implemented...
         # This model is not included because it is exactly the same as the
         # other model (same PEtab YAML and parameterization), hence has been
         # excluded from the candidate space.
-        # ('model_subspace_3', {f'k{i}': ESTIMATE for i in range(1,5)}),
+        ("model_subspace_3", {f"k{i}": ESTIMATE for i in range(1, 5)}),
     ]
 
     models = [
@@ -92,71 +89,71 @@ def test_model_space_backward_virtual(model_space):
     ]
 
     # Search found only expected models.
-    assert all([model in expected_models for model in models])
+    assert all(model in expected_models for model in models)
     # All expected models have now been added to the candidate space.
-    assert all([model in models for model in expected_models])
+    assert all(model in models for model in expected_models)
     # Probably unnecessary: same number of models in expectation vs realization
     assert len(expected_models) == len(candidate_space.models)
 
 
 def test_model_space_brute_force_limit(model_space):
-    candidate_space = BruteForceCandidateSpace()
+    candidate_space = BruteForceCandidateSpace(criterion=Criterion.NLLH)
     model_space.search(candidate_space, limit=13)
 
     # There are fifteen total models in the model space. Limiting to 13 models should
     # result in all models except the last two models in the last model subspace.
     expected_models = [
         (
-            'model_subspace_1',
-            {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0.0},
+            "model_subspace_1",
+            {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": 0.0},
         ),
         (
-            'model_subspace_1',
-            {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0.1},
+            "model_subspace_1",
+            {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": 0.1},
         ),
         (
-            'model_subspace_1',
-            {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': ESTIMATE},
+            "model_subspace_1",
+            {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": ESTIMATE},
         ),
         (
-            'model_subspace_1',
-            {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0.0},
+            "model_subspace_1",
+            {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0.0},
         ),
         (
-            'model_subspace_1',
-            {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0.1},
+            "model_subspace_1",
+            {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0.1},
         ),
         (
-            'model_subspace_1',
-            {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': ESTIMATE},
+            "model_subspace_1",
+            {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": ESTIMATE},
         ),
         (
-            'model_subspace_1',
-            {'k1': ESTIMATE, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0.0},
+            "model_subspace_1",
+            {"k1": ESTIMATE, "k2": 0.1, "k3": ESTIMATE, "k4": 0.0},
         ),
         (
-            'model_subspace_1',
-            {'k1': ESTIMATE, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0.1},
+            "model_subspace_1",
+            {"k1": ESTIMATE, "k2": 0.1, "k3": ESTIMATE, "k4": 0.1},
         ),
         (
-            'model_subspace_1',
-            {'k1': ESTIMATE, 'k2': 0.1, 'k3': ESTIMATE, 'k4': ESTIMATE},
+            "model_subspace_1",
+            {"k1": ESTIMATE, "k2": 0.1, "k3": ESTIMATE, "k4": ESTIMATE},
         ),
         (
-            'model_subspace_1',
-            {'k1': ESTIMATE, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0.0},
+            "model_subspace_1",
+            {"k1": ESTIMATE, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0.0},
         ),
         (
-            'model_subspace_1',
-            {'k1': ESTIMATE, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0.1},
+            "model_subspace_1",
+            {"k1": ESTIMATE, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0.1},
         ),
         (
-            'model_subspace_1',
-            {'k1': ESTIMATE, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': ESTIMATE},
+            "model_subspace_1",
+            {"k1": ESTIMATE, "k2": ESTIMATE, "k3": ESTIMATE, "k4": ESTIMATE},
         ),
         (
-            'model_subspace_2',
-            {'k1': 0.0, 'k2': 0.0, 'k3': 0.0, 'k4': ESTIMATE},
+            "model_subspace_2",
+            {"k1": 0.0, "k2": 0.0, "k3": 0.0, "k4": ESTIMATE},
         ),
     ]
 
@@ -166,14 +163,14 @@ def test_model_space_brute_force_limit(model_space):
     ]
 
     # Search found only expected models.
-    assert all([model in expected_models for model in models])
+    assert all(model in expected_models for model in models)
     # All expected models have now been added to the candidate space.
-    assert all([model in models for model in expected_models])
+    assert all(model in models for model in expected_models)
     # Probably unnecessary: same number of models in expectation vs realization
     assert len(expected_models) == len(candidate_space.models)
 
 
-'''
+"""
 @pytest.fixture
 def e():
     return ESTIMATE
@@ -258,4 +255,4 @@ def test_distance(model_space, e):
     model_space.reset()
     neighbors = model_space.neighbors(brute_force_candidate_space)
     assert len(neighbors) == 16
-'''
+"""
diff --git a/test/model_subspace/test_model_subspace.py b/test/model_subspace/test_model_subspace.py
index 71dbff04..5d3d6de9 100644
--- a/test/model_subspace/test_model_subspace.py
+++ b/test/model_subspace/test_model_subspace.py
@@ -13,9 +13,9 @@
 )
 from petab_select.constants import (
     ESTIMATE,
-    MODEL_SUBSPACE_ID,
     PARAMETER_VALUE_DELIMITER,
     PETAB_YAML,
+    Criterion,
 )
 from petab_select.model import Model
 from petab_select.model_subspace import ModelSubspace
@@ -23,17 +23,17 @@
 
 @pytest.fixture
 def model_subspace_id_and_definition() -> pd.Series:
-    model_subspace_id = 'model_subspace_1'
+    model_subspace_id = "model_subspace_1"
     data = {
         PETAB_YAML: Path(__file__).parent.parent.parent
-        / 'doc'
-        / 'examples'
-        / 'model_selection'
-        / 'petab_problem.yaml',
-        'k1': 0.2,
-        'k2': PARAMETER_VALUE_DELIMITER.join(['0.1', ESTIMATE]),
-        'k3': ESTIMATE,
-        'k4': PARAMETER_VALUE_DELIMITER.join(['0', '0.1', ESTIMATE]),
+        / "doc"
+        / "examples"
+        / "model_selection"
+        / "petab_problem.yaml",
+        "k1": 0.2,
+        "k2": PARAMETER_VALUE_DELIMITER.join(["0.1", ESTIMATE]),
+        "k3": ESTIMATE,
+        "k4": PARAMETER_VALUE_DELIMITER.join(["0", "0.1", ESTIMATE]),
     }
     return model_subspace_id, pd.Series(data=data, dtype=str)
 
@@ -49,16 +49,16 @@ def model_subspace(model_subspace_id_and_definition) -> ModelSubspace:
 
 @pytest.fixture
 def initial_model(model_subspace) -> Model:
-    estimated_parameters = ['k3', 'k4']
+    estimated_parameters = ["k3", "k4"]
     model = one(
         model_subspace.get_models(estimated_parameters=estimated_parameters)
     )
     # Initial model is parameterized as expected.
     assert model.parameters == {
-        'k1': 0.2,
-        'k2': 0.1,
-        'k3': ESTIMATE,
-        'k4': ESTIMATE,
+        "k1": 0.2,
+        "k2": 0.1,
+        "k3": ESTIMATE,
+        "k4": ESTIMATE,
     }
     return model
 
@@ -66,66 +66,64 @@ def initial_model(model_subspace) -> Model:
 def test_from_definition(model_subspace):
     """A model subspace definition is parsed correctly."""
     # Model subspace ID is parsed
-    assert model_subspace.model_subspace_id == 'model_subspace_1'
+    assert model_subspace.model_subspace_id == "model_subspace_1"
     # PEtab YAML is parsed
     assert model_subspace.petab_yaml.samefile(
         Path(__file__).parent.parent.parent
-        / 'doc'
-        / 'examples'
-        / 'model_selection'
-        / 'petab_problem.yaml',
+        / "doc"
+        / "examples"
+        / "model_selection"
+        / "petab_problem.yaml",
     )
     # Fixed parameters are parsed
-    assert model_subspace.parameters['k1'] == [0.2]
+    assert model_subspace.parameters["k1"] == [0.2]
     # Parameters with multiple values are parsed
     assert (
-        0.1 in model_subspace.parameters['k2']
-        and ESTIMATE in model_subspace.parameters['k2']
+        0.1 in model_subspace.parameters["k2"]
+        and ESTIMATE in model_subspace.parameters["k2"]
     )
     # Estimated parameters are parsed
-    assert model_subspace.parameters['k3'] == [ESTIMATE]
+    assert model_subspace.parameters["k3"] == [ESTIMATE]
 
 
 def test_get_models(model_subspace):
     """The getter for models with specific estimated parameters works."""
-    estimated_parameters = ['k2', 'k3']
+    estimated_parameters = ["k2", "k3"]
     models = list(
         model_subspace.get_models(estimated_parameters=estimated_parameters)
     )
     expected_parameterizations = [
-        {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0.0},
-        {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0.1},
+        {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0.0},
+        {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0.1},
     ]
     test_parameterizations = [model.parameters for model in models]
     # Getter gets only expected models.
     assert all(
-        [
-            test_parameterization in expected_parameterizations
-            for test_parameterization in expected_parameterizations
-        ]
+        test_parameterization in expected_parameterizations
+        for test_parameterization in expected_parameterizations
     )
     # Getter gets all expected models.
     assert all(
-        [
-            expected_parameterization in test_parameterizations
-            for expected_parameterization in expected_parameterizations
-        ]
+        expected_parameterization in test_parameterizations
+        for expected_parameterization in expected_parameterizations
     )
 
 
 def test_search_forward(model_subspace, initial_model):
     # TODO exclude history, use limit
-    candidate_space = ForwardCandidateSpace(predecessor_model=initial_model)
+    candidate_space = ForwardCandidateSpace(
+        predecessor_model=initial_model, criterion=Criterion.NLLH
+    )
 
     model_subspace.search(candidate_space=candidate_space)
     # Only one model is possible in the forward direction.
     assert len(candidate_space.models) == 1
     # The one model has the expected parameterization.
     expected_parameterization = {
-        'k1': 0.2,
-        'k2': ESTIMATE,
-        'k3': ESTIMATE,
-        'k4': ESTIMATE,
+        "k1": 0.2,
+        "k2": ESTIMATE,
+        "k3": ESTIMATE,
+        "k4": ESTIMATE,
     }
     assert one(candidate_space.models).parameters == expected_parameterization
 
@@ -133,7 +131,7 @@ def test_search_forward(model_subspace, initial_model):
     # FIXME currently only returns 1 model anyway
     limit_considered_candidates = 1
     model_subspace.reset_exclusions()
-    candidate_space.reset(predecessor_model=initial_model)
+    candidate_space.reset(predecessor_model=initial_model, excluded_hashes=[])
     model_subspace.search(
         candidate_space=candidate_space,
         limit=limit_considered_candidates,
@@ -145,37 +143,35 @@ def test_search_forward(model_subspace, initial_model):
 
 def test_search_backward(model_subspace, initial_model):
     # TODO exclude history, use limit
-    candidate_space = BackwardCandidateSpace(predecessor_model=initial_model)
+    candidate_space = BackwardCandidateSpace(
+        predecessor_model=initial_model, criterion=Criterion.NLLH
+    )
 
     model_subspace.search(candidate_space=candidate_space)
     # Only two models are possible in the backward direction.
     assert len(candidate_space.models) == 2
     expected_parameterizations = [
-        {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0},
-        {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0.1},
+        {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": 0},
+        {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": 0.1},
     ]
     test_parameterizations = [
         model.parameters for model in candidate_space.models
     ]
     # Search found only expected models.
     assert all(
-        [
-            test_parameterization in expected_parameterizations
-            for test_parameterization in test_parameterizations
-        ]
+        test_parameterization in expected_parameterizations
+        for test_parameterization in test_parameterizations
     )
     # Search found all expected models.
     assert all(
-        [
-            expected_parameterization in test_parameterizations
-            for expected_parameterization in expected_parameterizations
-        ]
+        expected_parameterization in test_parameterizations
+        for expected_parameterization in expected_parameterizations
     )
 
     # Test limit via model subspace
     limit_considered_candidates = 1
     model_subspace.reset_exclusions()
-    candidate_space.reset(predecessor_model=initial_model)
+    candidate_space.reset(predecessor_model=initial_model, excluded_hashes=[])
     model_subspace.search(
         candidate_space=candidate_space,
         limit=limit_considered_candidates,
@@ -187,40 +183,36 @@ def test_search_backward(model_subspace, initial_model):
 
 def test_search_brute_force(model_subspace):
     # TODO exclude history, use limit
-    candidate_space = BruteForceCandidateSpace()
+    candidate_space = BruteForceCandidateSpace(criterion=Criterion.NLLH)
 
     model_subspace.search(candidate_space=candidate_space)
     # All models (6) are accepted as candidates.
     assert len(candidate_space.models) == 6
 
     expected_parameterizations = [
-        {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0},
-        {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': 0.1},
-        {'k1': 0.2, 'k2': 0.1, 'k3': ESTIMATE, 'k4': ESTIMATE},
-        {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0},
-        {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0.1},
-        {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': ESTIMATE},
+        {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": 0},
+        {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": 0.1},
+        {"k1": 0.2, "k2": 0.1, "k3": ESTIMATE, "k4": ESTIMATE},
+        {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0},
+        {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0.1},
+        {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": ESTIMATE},
     ]
     test_parameterizations = [
         model.parameters for model in candidate_space.models
     ]
     # Search found only expected models.
     assert all(
-        [
-            test_parameterization in expected_parameterizations
-            for test_parameterization in test_parameterizations
-        ]
+        test_parameterization in expected_parameterizations
+        for test_parameterization in test_parameterizations
     )
     # Search found all expected models.
     assert all(
-        [
-            expected_parameterization in test_parameterizations
-            for expected_parameterization in expected_parameterizations
-        ]
+        expected_parameterization in test_parameterizations
+        for expected_parameterization in expected_parameterizations
     )
 
     limit_accepted_candidates = 3
-    candidate_space.reset(limit=limit_accepted_candidates)
+    candidate_space.reset(limit=limit_accepted_candidates, excluded_hashes=[])
     model_subspace.search(candidate_space=candidate_space)
     """ FIXME remove, since models now have to be explicitly excluded. TODO Test exclusions via problem `add_calibrated_model`
     # Test exclusions: no models are in the candidate space as the model subspace
@@ -239,7 +231,7 @@ def test_search_brute_force(model_subspace):
     candidate_space.limit.set_limit(limit_accepted_candidates)
     with pytest.warns(
         RuntimeWarning,
-        match="Model has been previously excluded from the candidate space so is skipped here.",
+        match=r"Model .* has been previously excluded from the candidate space so is skipped here.",
     ) as warning_record:
         model_subspace.search(candidate_space=candidate_space)
     test_parameterizations = [
@@ -252,25 +244,21 @@ def test_search_brute_force(model_subspace):
     assert len(candidate_space.models) == limit_accepted_candidates
     # Search found only expected models.
     assert all(
-        [
-            test_parameterization in expected_parameterizations
-            for test_parameterization in test_parameterizations
-        ]
+        test_parameterization in expected_parameterizations
+        for test_parameterization in test_parameterizations
     )
     # Test exclusions: all models have now been added to the candidate space.
     # TODO ideally with only 3 additional calls to `candidate_space.consider`, assuming
     #      the model_subspace excluded the first three models it had already sent.
     assert all(
-        [
-            expected_parameterization in test_parameterizations
-            for expected_parameterization in expected_parameterizations
-        ]
+        expected_parameterization in test_parameterizations
+        for expected_parameterization in expected_parameterizations
     )
 
     # Test limit via model subspace
     limit_considered_candidates = 1
     model_subspace.reset_exclusions()
-    candidate_space.reset()
+    candidate_space.reset(excluded_hashes=[])
     model_subspace.search(
         candidate_space=candidate_space,
         limit=limit_considered_candidates,
@@ -282,39 +270,37 @@ def test_search_brute_force(model_subspace):
 
 def test_search_swap(model_subspace, initial_model):
     # TODO exclude history, use limit
-    candidate_space = LateralCandidateSpace(predecessor_model=initial_model)
+    candidate_space = LateralCandidateSpace(
+        predecessor_model=initial_model, criterion=Criterion.NLLH
+    )
 
     model_subspace.search(candidate_space=candidate_space)
     # Only two models are possible in the swap direction.
     assert len(candidate_space.models) == 2
     # The two models have the expected parameterization.
     expected_parameterizations = [
-        {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0},
-        {'k1': 0.2, 'k2': ESTIMATE, 'k3': ESTIMATE, 'k4': 0.1},
+        {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0},
+        {"k1": 0.2, "k2": ESTIMATE, "k3": ESTIMATE, "k4": 0.1},
     ]
     test_parameterizations = [
         model.parameters for model in candidate_space.models
     ]
     # Search found only expected models.
     assert all(
-        [
-            test_parameterization in expected_parameterizations
-            for test_parameterization in test_parameterizations
-        ]
+        test_parameterization in expected_parameterizations
+        for test_parameterization in test_parameterizations
     )
     # Search found all expected models.
     assert all(
-        [
-            expected_parameterization in test_parameterizations
-            for expected_parameterization in expected_parameterizations
-        ]
+        expected_parameterization in test_parameterizations
+        for expected_parameterization in expected_parameterizations
     )
 
     # Test limit via model subspace
     # FIXME currently only returns 1 model anyway
     limit_considered_candidates = 1
     model_subspace.reset_exclusions()
-    candidate_space.reset(predecessor_model=initial_model)
+    candidate_space.reset(predecessor_model=initial_model, excluded_hashes=[])
     model_subspace.search(
         candidate_space=candidate_space,
         limit=limit_considered_candidates,
diff --git a/test/pypesto/generate_expected_models.py b/test/pypesto/generate_expected_models.py
index 081b9dbe..7d68bd7d 100644
--- a/test/pypesto/generate_expected_models.py
+++ b/test/pypesto/generate_expected_models.py
@@ -2,15 +2,11 @@
 from pathlib import Path
 
 import fides
-import pandas as pd
 import pypesto.engine
 import pypesto.optimize
 import pypesto.select
-from more_itertools import one
 
 import petab_select
-from petab_select import Model
-from petab_select.constants import CRITERIA, ESTIMATED_PARAMETERS, MODEL
 
 SKIP_TEST_CASES_WITH_PREEXISTING_EXPECTED_MODEL = False
 os.environ["AMICI_EXPERIMENTAL_SBML_NONCONST_CLS"] = "1"
@@ -23,20 +19,20 @@
 
 # Do not use computationally-expensive test cases in CI
 skip_test_cases = [
-    '0009',
+    "0009",
 ]
 
-test_cases_path = Path(__file__).resolve().parent.parent.parent / 'test_cases'
+test_cases_path = Path(__file__).resolve().parent.parent.parent / "test_cases"
 
 # Reduce runtime but with high reproducibility
 minimize_options = {
-    'n_starts': 24,
-    'optimizer': pypesto.optimize.FidesOptimizer(
+    "n_starts": 24,
+    "optimizer": pypesto.optimize.FidesOptimizer(
         verbose=0, hessian_update=fides.BFGS()
     ),
-    'engine': pypesto.engine.MultiProcessEngine(),
-    'filename': None,
-    'progress_bar': False,
+    "engine": pypesto.engine.MultiProcessEngine(),
+    "filename": None,
+    "progress_bar": False,
 }
 
 
@@ -47,14 +43,14 @@ def objective_customizer(obj):
 
 # Indentation to match `test_pypesto.py`, to make it easier to keep files similar.
 if True:
-    for test_case_path in test_cases_path.glob('*'):
+    for test_case_path in test_cases_path.glob("*"):
         if test_cases and test_case_path.stem not in test_cases:
             continue
 
         if test_case_path.stem in skip_test_cases:
             continue
 
-        expected_model_yaml = test_case_path / 'expected.yaml'
+        expected_model_yaml = test_case_path / "expected.yaml"
 
         if (
             SKIP_TEST_CASES_WITH_PREEXISTING_EXPECTED_MODEL
@@ -62,11 +58,11 @@ def objective_customizer(obj):
         ):
             # Skip test cases that already have an expected model.
             continue
-        print(f'Running test case {test_case_path.stem}')
+        print(f"Running test case {test_case_path.stem}")
 
         # Setup the pyPESTO model selector instance.
         petab_select_problem = petab_select.Problem.from_yaml(
-            test_case_path / 'petab_select_problem.yaml',
+            test_case_path / "petab_select_problem.yaml",
         )
         pypesto_select_problem = pypesto.select.Problem(
             petab_select_problem=petab_select_problem
diff --git a/test/pypesto/regenerate_model_hashes.py b/test/pypesto/regenerate_model_hashes.py
new file mode 100644
index 00000000..95fdfb2a
--- /dev/null
+++ b/test/pypesto/regenerate_model_hashes.py
@@ -0,0 +1,34 @@
+from pathlib import Path
+
+import yaml
+
+import petab_select
+from petab_select import (
+    MODEL_HASH,
+    MODEL_ID,
+    MODEL_SUBSPACE_ID,
+    MODEL_SUBSPACE_INDICES,
+    PREDECESSOR_MODEL_HASH,
+)
+
+test_cases_path = Path(__file__).resolve().parent.parent.parent / "test_cases"
+
+
+for test_case_path in test_cases_path.glob("*"):
+    petab_select_problem = petab_select.Problem.from_yaml(
+        test_case_path / "petab_select_problem.yaml",
+    )
+    expected_model_yaml = test_case_path / "expected.yaml"
+
+    with open(expected_model_yaml) as f:
+        model_dict = yaml.safe_load(f)
+
+    model = petab_select_problem.model_space.model_subspaces[
+        model_dict[MODEL_SUBSPACE_ID]
+    ].indices_to_model(model_dict[MODEL_SUBSPACE_INDICES])
+    model_dict[MODEL_ID] = str(model.model_id)
+    model_dict[MODEL_HASH] = str(model.get_hash())
+    model_dict[PREDECESSOR_MODEL_HASH] = None
+
+    with open(expected_model_yaml, "w") as f:
+        yaml.safe_dump(model_dict, f)
diff --git a/test/pypesto/test_pypesto.py b/test/pypesto/test_pypesto.py
index 04da0da5..de6f2576 100644
--- a/test/pypesto/test_pypesto.py
+++ b/test/pypesto/test_pypesto.py
@@ -1,43 +1,43 @@
 import os
 from pathlib import Path
 
-import fides
 import numpy as np
 import pandas as pd
 import pypesto.engine
 import pypesto.optimize
 import pypesto.select
 import pytest
-from more_itertools import one
 
 import petab_select
 from petab_select import Model
-from petab_select.constants import CRITERIA, ESTIMATED_PARAMETERS, MODEL
+from petab_select.constants import (
+    CRITERIA,
+    ESTIMATED_PARAMETERS,
+)
 
 os.environ["AMICI_EXPERIMENTAL_SBML_NONCONST_CLS"] = "1"
 
 # Set to `[]` to test all
 test_cases = [
-    #'0004',
-    #'0008',
+    # '0006',
+    # '0002',
+    # '0008',
+    # "0009",
 ]
 
 # Do not use computationally-expensive test cases in CI
 skip_test_cases = [
-    '0009',
+    "0009",
 ]
 
-test_cases_path = Path(__file__).resolve().parent.parent.parent / 'test_cases'
+test_cases_path = Path(__file__).resolve().parent.parent.parent / "test_cases"
 
 # Reduce runtime but with high reproducibility
 minimize_options = {
-    'n_starts': 10,
-    'optimizer': pypesto.optimize.FidesOptimizer(
-        verbose=0, hessian_update=fides.BFGS()
-    ),
-    'engine': pypesto.engine.MultiProcessEngine(),
-    'filename': None,
-    'progress_bar': False,
+    "n_starts": 10,
+    "engine": pypesto.engine.MultiProcessEngine(),
+    "filename": None,
+    "progress_bar": False,
 }
 
 
@@ -46,48 +46,53 @@ def objective_customizer(obj):
     obj.amici_solver.setRelativeTolerance(1e-12)
 
 
-@pytest.mark.skip(reason="FIXME")
-def test_pypesto():
-    for test_case_path in test_cases_path.glob('*'):
-        if test_cases and test_case_path.stem not in test_cases:
-            continue
-
-        if test_case_path.stem in skip_test_cases:
-            continue
-
-        expected_model_yaml = test_case_path / 'expected.yaml'
-        # Setup the pyPESTO model selector instance.
-        petab_select_problem = petab_select.Problem.from_yaml(
-            test_case_path / 'petab_select_problem.yaml',
-        )
-        pypesto_select_problem = pypesto.select.Problem(
-            petab_select_problem=petab_select_problem
-        )
-
-        # Run the selection process until "exhausted".
-        pypesto_select_problem.select_to_completion(
-            minimize_options=minimize_options,
-            objective_customizer=objective_customizer,
-        )
-
-        # Get the best model
-        best_model = petab_select_problem.get_best(
-            models=pypesto_select_problem.calibrated_models.values(),
+@pytest.mark.parametrize(
+    "test_case_path_stem",
+    sorted(
+        [test_case_path.stem for test_case_path in test_cases_path.glob("*")]
+    ),
+)
+def test_pypesto(test_case_path_stem):
+    if test_cases and test_case_path_stem not in test_cases:
+        pytest.skip("Test excluded from subset selected for debugging.")
+
+    if test_case_path_stem in skip_test_cases:
+        pytest.skip("Test marked to be skipped.")
+
+    test_case_path = test_cases_path / test_case_path_stem
+    expected_model_yaml = test_case_path / "expected.yaml"
+    # Setup the pyPESTO model selector instance.
+    petab_select_problem = petab_select.Problem.from_yaml(
+        test_case_path / "petab_select_problem.yaml",
+    )
+    pypesto_select_problem = pypesto.select.Problem(
+        petab_select_problem=petab_select_problem
+    )
+
+    # Run the selection process until "exhausted".
+    pypesto_select_problem.select_to_completion(
+        minimize_options=minimize_options,
+        objective_customizer=objective_customizer,
+    )
+
+    # Get the best model
+    best_model = petab_select_problem.get_best(
+        models=pypesto_select_problem.calibrated_models.values(),
+    )
+
+    # Load the expected model.
+    expected_model = Model.from_yaml(expected_model_yaml)
+
+    def get_series(model, dict_attribute) -> pd.Series:
+        return pd.Series(
+            getattr(model, dict_attribute),
+            dtype=np.float64,
+        ).sort_index()
+
+    # The estimated parameters and criteria values are as expected.
+    for dict_attribute in [CRITERIA, ESTIMATED_PARAMETERS]:
+        pd.testing.assert_series_equal(
+            get_series(expected_model, dict_attribute),
+            get_series(best_model, dict_attribute),
+            atol=1e-2,
         )
-
-        # Load the expected model.
-        expected_model = Model.from_yaml(expected_model_yaml)
-
-        def get_series(model, dict_attribute) -> pd.Series:
-            return pd.Series(
-                getattr(model, dict_attribute),
-                dtype=np.float64,
-            ).sort_index()
-
-        # The estimated parameters and criteria values are as expected.
-        for dict_attribute in [CRITERIA, ESTIMATED_PARAMETERS]:
-            pd.testing.assert_series_equal(
-                get_series(expected_model, dict_attribute),
-                get_series(best_model, dict_attribute),
-                atol=1e-2,
-            )
diff --git a/test/ui/__init__.py b/test/ui/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/test/ui/test_ui.py b/test/ui/test_ui.py
new file mode 100644
index 00000000..dcfaac7f
--- /dev/null
+++ b/test/ui/test_ui.py
@@ -0,0 +1,70 @@
+from pathlib import Path
+
+import pytest
+from more_itertools import one
+
+import petab_select
+from petab_select.constants import (
+    CANDIDATE_SPACE,
+    MODELS,
+    UNCALIBRATED_MODELS,
+)
+
+
+@pytest.fixture
+def petab_select_problem():
+    return petab_select.Problem.from_yaml(
+        Path(__file__).parents[2]
+        / "doc"
+        / "examples"
+        / "model_selection"
+        / "petab_select_problem.yaml"
+    )
+
+
+def test_user_calibrated_models(petab_select_problem):
+    """Test handling of user-calibrated models."""
+    model_M1_2 = petab_select_problem.model_space.model_subspaces[
+        "M1_2"
+    ].indices_to_model((0, 0, 0))
+    model_M1_2.set_criterion(
+        criterion=petab_select_problem.criterion, value=12.3
+    )
+    user_calibrated_models = {model_M1_2.get_hash(): model_M1_2}
+
+    # Initial iteration: expect the "empty" model. Set dummy criterion and continue.
+    iteration = petab_select.ui.start_iteration(
+        problem=petab_select_problem,
+        user_calibrated_models=user_calibrated_models,
+    )
+    model_M1_0 = one(iteration[UNCALIBRATED_MODELS])
+    # The initial iteration proceeded as expected: the "empty" model was identified as a candidate.
+    assert model_M1_0.model_subspace_id == "M1_0"
+    model_M1_0.set_criterion(petab_select_problem.criterion, 100)
+    iteration_results = petab_select.ui.end_iteration(
+        candidate_space=iteration[CANDIDATE_SPACE],
+        calibrated_models=[model_M1_0],
+    )
+
+    # Second iteration. User calibrated models should now change behavior.
+    iteration = petab_select.ui.start_iteration(
+        problem=petab_select_problem,
+        candidate_space=iteration_results[CANDIDATE_SPACE],
+        user_calibrated_models=user_calibrated_models,
+    )
+    # The user calibrated model was not included in the iteration's uncalibrated models.
+    uncalibrated_model_ids = [
+        model.model_subspace_id for model in iteration[UNCALIBRATED_MODELS]
+    ]
+    assert set(uncalibrated_model_ids) == {"M1_1", "M1_3"}
+    for uncalibrated_model in iteration[UNCALIBRATED_MODELS]:
+        uncalibrated_model.set_criterion(petab_select_problem.criterion, 50)
+    iteration_results = petab_select.ui.end_iteration(
+        candidate_space=iteration[CANDIDATE_SPACE],
+        calibrated_models=iteration[UNCALIBRATED_MODELS],
+    )
+    iteration_model_ids = [
+        model.model_subspace_id for model in iteration_results[MODELS]
+    ]
+    # The user-calibrated model is included in the final set of models for this iteration
+    assert set(iteration_model_ids) == {"M1_1", "M1_2", "M1_3"}
diff --git a/test_cases/0001/expected.yaml b/test_cases/0001/expected.yaml
index ad139ca6..7149938b 100644
--- a/test_cases/0001/expected.yaml
+++ b/test_cases/0001/expected.yaml
@@ -3,8 +3,8 @@ criteria:
   NLLH: -4.0877025471033335
 estimated_parameters:
   sigma_x2: 0.1224643186838838
-model_hash: 4fbbda138c41c1d3c9bcd538fefd1f034ba746336eec0c18f7b6032246a29361b674027c62393209d6777d70436e793cd4ba39ebd9f217d5cc33b62797138254
-model_id: 4fbbda138c41c1d3c9bcd538fefd1f034ba746336eec0c18f7b6032246a29361b674027c62393209d6777d70436e793cd4ba39ebd9f217d5cc33b62797138254
+model_hash: M1_1-000
+model_id: M1_1-000
 model_subspace_id: M1_1
 model_subspace_indices:
 - 0
diff --git a/test_cases/0001/petab/parameters.tsv b/test_cases/0001/petab/parameters.tsv
index 026eb8c6..cfcc8636 100644
--- a/test_cases/0001/petab/parameters.tsv
+++ b/test_cases/0001/petab/parameters.tsv
@@ -1,5 +1,5 @@
 parameterId	parameterName	parameterScale	lowerBound	upperBound	nominalValue	estimate
-k1	k_{1}	lin	0	1.00E+03	0.2	1
-k2	k_{2}	lin	0	1.00E+03	0.1	1
-k3	k_{3}	lin	0	1.00E+03	0	1
+k1	k_{1}	lin	0	1	0.2	1
+k2	k_{2}	lin	0	1	0.1	1
+k3	k_{3}	lin	0	1	0	1
 sigma_x2	\sigma_{x2}	lin	1.00E-05	1.00E+03	0.15	1
diff --git a/test_cases/0002/expected.yaml b/test_cases/0002/expected.yaml
index 59a1ce81..c82acb72 100644
--- a/test_cases/0002/expected.yaml
+++ b/test_cases/0002/expected.yaml
@@ -4,8 +4,8 @@ criteria:
 estimated_parameters:
   k1: 0.20160877227137408
   sigma_x2: 0.11715051179648493
-model_hash: 6fd0da92d615c00aecbce338a9346edf4e2c6a7136d406a1bc007f96265a59dd261890845bc5baaeeb5f422dd1813122eb5d9f605ddb02cd032841cc6aaaca2a
-model_id: 6fd0da92d615c00aecbce338a9346edf4e2c6a7136d406a1bc007f96265a59dd261890845bc5baaeeb5f422dd1813122eb5d9f605ddb02cd032841cc6aaaca2a
+model_hash: M1_3-000
+model_id: M1_3-000
 model_subspace_id: M1_3
 model_subspace_indices:
 - 0
@@ -16,4 +16,4 @@ parameters:
   k2: 0.1
   k3: 0
 petab_yaml: ../0001/petab/petab_problem.yaml
-predecessor_model_hash: 1ba35a377e081501da8ea21c7bec90984baf680fdc0441e4d653350a0f03a27abccdaea569a116c0acc4cbfeb847ed460bc7e6035a229b0acfa0c078130b553a
+predecessor_model_hash: null
diff --git a/test_cases/0003/expected.yaml b/test_cases/0003/expected.yaml
index 8ca8d406..48a87a33 100644
--- a/test_cases/0003/expected.yaml
+++ b/test_cases/0003/expected.yaml
@@ -3,8 +3,8 @@ criteria:
   NLLH: -4.087702809249463
 estimated_parameters:
   sigma_x2: 0.12245324237132274
-model_hash: a56fafb3d43e25d3231e6f77d6b62e816bce3010fd0913fba51b67c35c9c281f77e8ffeb504e429c36b65e42c7f8617e6ea57d0a9ba416736e4ee60db9592056
-model_id: a56fafb3d43e25d3231e6f77d6b62e816bce3010fd0913fba51b67c35c9c281f77e8ffeb504e429c36b65e42c7f8617e6ea57d0a9ba416736e4ee60db9592056
+model_hash: M1-110
+model_id: M1-110
 model_subspace_id: M1
 model_subspace_indices:
 - 1
diff --git a/test_cases/0004/expected.yaml b/test_cases/0004/expected.yaml
index 4e1ff88a..811edc18 100644
--- a/test_cases/0004/expected.yaml
+++ b/test_cases/0004/expected.yaml
@@ -4,8 +4,8 @@ criteria:
 estimated_parameters:
   k1: 0.20160877435934813
   sigma_x2: 0.11714883276066365
-model_hash: bc5e8327701472fef89052d8fe1f6e6bd0e31b86a927bef6d54cf418b769a45c7eaae2ca5f1b260e33743f138ce5f6c01c24eb7fa43f51a1e52ed8831f309c69
-model_id: bc5e8327701472fef89052d8fe1f6e6bd0e31b86a927bef6d54cf418b769a45c7eaae2ca5f1b260e33743f138ce5f6c01c24eb7fa43f51a1e52ed8831f309c69
+model_hash: M1_3-000
+model_id: M1_3-000
 model_subspace_id: M1_3
 model_subspace_indices:
 - 0
@@ -16,4 +16,4 @@ parameters:
   k2: 0.1
   k3: 0
 petab_yaml: ../0001/petab/petab_problem.yaml
-predecessor_model_hash: 4884e4fce86df0d45d24bb23bfc4a4fa9b3b2de4d58c7baed06615ac6055557cce47a0c44b172eaee453826c7babda9dafcfc360ad72a0286eaa7a805d974488
+predecessor_model_hash: null
diff --git a/test_cases/0005/expected.yaml b/test_cases/0005/expected.yaml
index b219d18b..897a2432 100644
--- a/test_cases/0005/expected.yaml
+++ b/test_cases/0005/expected.yaml
@@ -4,8 +4,8 @@ criteria:
 estimated_parameters:
   k1: 0.20160877910494426
   sigma_x2: 0.11716072823171682
-model_hash: 41ab831961ee3e1bb3918796fc85941ca5d59c90e9c867278daa5c9f5ba7fc707fa9da35f093706a671890b500cdb657d75d7f30ccc3b2ec072d1ddb28b40e2f
-model_id: 41ab831961ee3e1bb3918796fc85941ca5d59c90e9c867278daa5c9f5ba7fc707fa9da35f093706a671890b500cdb657d75d7f30ccc3b2ec072d1ddb28b40e2f
+model_hash: M1_3-000
+model_id: M1_3-000
 model_subspace_id: M1_3
 model_subspace_indices:
 - 0
@@ -16,4 +16,4 @@ parameters:
   k2: 0.1
   k3: 0
 petab_yaml: ../0001/petab/petab_problem.yaml
-predecessor_model_hash: eef6129f57cab797a8c82d3e7c4a919e52d6ae155fe7a982485dd5cbb7eb2dab869bcec5e99ccca1867f749df79529d2758c0c687cfccee703392847a58edbc7
+predecessor_model_hash: null
diff --git a/test_cases/0006/expected.yaml b/test_cases/0006/expected.yaml
index 52941b81..efd80860 100644
--- a/test_cases/0006/expected.yaml
+++ b/test_cases/0006/expected.yaml
@@ -3,8 +3,8 @@ criteria:
   NLLH: -4.087701638723078
 estimated_parameters:
   sigma_x2: 0.12248840167611977
-model_hash: 0b5666da87c6c243d9e9fb85f7db5a7e12e733b522dd7caf688fea23cbcbb2e6f5cd345753820355d6472e595d6ccd30e96024ada3e66981fbe41c073f218da5
-model_id: 0b5666da87c6c243d9e9fb85f7db5a7e12e733b522dd7caf688fea23cbcbb2e6f5cd345753820355d6472e595d6ccd30e96024ada3e66981fbe41c073f218da5
+model_hash: M1_0-000
+model_id: M1_0-000
 model_subspace_id: M1_0
 model_subspace_indices:
 - 0
@@ -15,4 +15,4 @@ parameters:
   k2: 0.1
   k3: 0
 petab_yaml: ../0001/petab/petab_problem.yaml
-predecessor_model_hash: virtual_initial_model
+predecessor_model_hash: null
diff --git a/test_cases/0007/expected.yaml b/test_cases/0007/expected.yaml
index 489ddae9..b843cd92 100644
--- a/test_cases/0007/expected.yaml
+++ b/test_cases/0007/expected.yaml
@@ -2,8 +2,8 @@ criteria:
   AIC: 11.117195852885663
   NLLH: 5.558597926442832
 estimated_parameters: {}
-model_hash: 76cd33b4662f5d1aeab6a06c4aea1a74d7ed1d937c8302d4d12d4f4bc30638a52c0e5e7a30b7647aca928813e2b845db1447cfa224f8eec49d6012a541073344
-model_id: 76cd33b4662f5d1aeab6a06c4aea1a74d7ed1d937c8302d4d12d4f4bc30638a52c0e5e7a30b7647aca928813e2b845db1447cfa224f8eec49d6012a541073344
+model_hash: M1_0-000
+model_id: M1_0-000
 model_subspace_id: M1_0
 model_subspace_indices:
 - 0
@@ -14,4 +14,4 @@ parameters:
   k2: 0.1
   k3: 0
 petab_yaml: petab/petab_problem.yaml
-predecessor_model_hash: virtual_initial_model
+predecessor_model_hash: null
diff --git a/test_cases/0008/expected.yaml b/test_cases/0008/expected.yaml
index 4c9aa686..0fb56440 100644
--- a/test_cases/0008/expected.yaml
+++ b/test_cases/0008/expected.yaml
@@ -2,8 +2,8 @@ criteria:
   AICc: 11.117195852885663
   NLLH: 5.558597926442832
 estimated_parameters: {}
-model_hash: 55257e66db40f0afe00d8a79e7794512f9a1b784b6f3f46afda685d7da6d6b16972a126dc9ffc87194cd033d82c9899d9fb9516d085b418ba7684446943bfb09
-model_id: 55257e66db40f0afe00d8a79e7794512f9a1b784b6f3f46afda685d7da6d6b16972a126dc9ffc87194cd033d82c9899d9fb9516d085b418ba7684446943bfb09
+model_hash: M1_0-000
+model_id: M1_0-000
 model_subspace_id: M1_0
 model_subspace_indices:
 - 0
@@ -14,4 +14,4 @@ parameters:
   k2: 0.1
   k3: 0
 petab_yaml: ../0007/petab/petab_problem.yaml
-predecessor_model_hash: e90911f79cb469da6acd814744c6440dd449cc496ef56c4221956930fe41b4b7f561e61f8051112b87042764880b8650ee3c008c5dce973a4361a99694ab254b
+predecessor_model_hash: null
diff --git a/test_cases/0009/expected.yaml b/test_cases/0009/expected.yaml
index ed423380..1c0260c3 100644
--- a/test_cases/0009/expected.yaml
+++ b/test_cases/0009/expected.yaml
@@ -10,8 +10,8 @@ estimated_parameters:
   a_k12_k05k12: 8.26789880667234
   a_k12k16_k08k12k16: 33.038691003614964
   a_k16_k12k16: 10.424836834041892
-model_hash: b4b3d9243ed929dc84e4d0a007a5deac94612239089ebce31ed57a2aa5244467bf65a5035d7b5e77dfdb8d89a42a22a88ec68bc038da95a0aefb5f4cc6836692
-model_id: b4b3d9243ed929dc84e4d0a007a5deac94612239089ebce31ed57a2aa5244467bf65a5035d7b5e77dfdb8d89a42a22a88ec68bc038da95a0aefb5f4cc6836692
+model_hash: M-01000100001000010010000000010001
+model_id: M-01000100001000010010000000010001
 model_subspace_id: M
 model_subspace_indices:
 - 0
@@ -80,4 +80,4 @@ parameters:
   a_k16_k08k16: 1
   a_k16_k12k16: estimate
 petab_yaml: petab/petab_problem.yaml
-predecessor_model_hash: 388df9ee97062984a88432f56c52c3e5ee19ba6840c3375bb07625f8ffb5f0090413ed60b43cc4c90319b52bf26af7de7c876fdbf69544df6d551c8aef519e69
+predecessor_model_hash: null
diff --git a/test_cases/0009/predecessor_model.yaml b/test_cases/0009/predecessor_model.yaml
index c2cba72d..4471224b 100644
--- a/test_cases/0009/predecessor_model.yaml
+++ b/test_cases/0009/predecessor_model.yaml
@@ -1,8 +1,8 @@
 criteria:
   NLLH: inf
 estimated_parameters: {}
-model_hash: cf9e9633bb9566b26275cc273997fe8c873e4b017e84da13889951f93b6a2950b334af9cc97b582bd43626834d09cd028803056c97dd4692f1e4b209258752a7
-model_id: cf9e9633bb9566b26275cc273997fe8c873e4b017e84da13889951f93b6a2950b334af9cc97b582bd43626834d09cd028803056c97dd4692f1e4b209258752a7
+model_hash: M-01000001111110010011010011010001
+model_id: M-01000001111110010011010011010001
 model_subspace_id: M
 model_subspace_indices:
 - 0
@@ -71,4 +71,4 @@ parameters:
   a_k16_k08k16: 1
   a_k16_k12k16: estimate
 petab_yaml: petab/petab_problem.yaml
-predecessor_model_hash: None
+predecessor_model_hash: null