Skip to content

Note

Click here to download the full example code

Manage model results

Manage model results using several result managers.

from __future__ import annotations

import logging

import mlflow
from gemseo.datasets.dataset import Dataset
from gemseo.post.dataset.bars import BarPlot
from mlflow import delete_run
from numpy import atleast_1d
from pandas import DataFrame
from pandas import concat

from vimseo import EXAMPLE_RUNS_DIR
from vimseo.api import activate_logger
from vimseo.api import create_model
from vimseo.core.model_result import ModelResult
from vimseo.core.model_settings import IntegratedModelSettings
from vimseo.storage_management.base_storage_manager import PersistencyPolicy
from vimseo.utilities.plotting_utils import plot_curves

activate_logger(level=logging.INFO)

Properly storing model results when running hundreds of simulations with possibly several users and several studies at the same time is a key aspect, especially if model credibility demonstration is targeted. Our strategy is to separate the following concepts: - scratch results: these are specific to models relying on external solvers. A scratch directory contains the data read or written by the solver. - archive results: these are the data we want to keep after the simulation. They comprise the input and output data, metadata and files generated in the scratch directory that we want to conserve. - a cache: while a cache could be seen as a way of archiving results, we prefer to separate cache and archiving. However, a bridge is available between both: a cache can be generated from an archive or part of an archive.

An archive manager can be chosen among DirectoryArchive or MlflowArchive. In the first case, an arborescence of directories is generated under a directory default_archive created in the current working directory. In the second case, an MLflow database is created. By default the database is file-based and is created under a directory mlflow_archive in the current working directory. The root directory of the archive can be specified with argument directory_archive_root passed to the constructor of the model or to create_model().

The scratch results are stored in an arborescence of directories similarly to a DirectoryArchive.

The persistency policy of the scratch and the archive can be specified independently.

By default, the storage generates unique directories at each new model execution. For a DirectoryArchive, the argument job_name allows to store the result in a specific directory without creating unique directories.

Here, a model is created with the default DirectoryArchive manager, and a specific job directory is used to store the archive result. The _accept_overwrite_job_dir attribute must be explicitly set to True. The MockModelPersistent model has the specificity of requiring to store some generated files to the archive. The archive manager automatically handles the copy of these files from the scratch to the archive. Here, the scratch directories are kept such that the user can look into them:

model_name = "MockModelPersistent"
load_case = "LC1"
model = create_model(
    model_name,
    load_case,
    IntegratedModelSettings(
        directory_scratch_persistency=PersistencyPolicy.DELETE_NEVER,
        directory_archive_root=EXAMPLE_RUNS_DIR / "archive/visualize_model_result",
        directory_scratch_root=EXAMPLE_RUNS_DIR / "scratch/visualize_model_result",
        cache_file_path=EXAMPLE_RUNS_DIR
        / f"caches/visualize_model_result/{model_name}_{load_case}_cache.hdf",
    ),
)
model.archive_manager._accept_overwrite_job_dir = True
model.cache = None

An execution is then performed. A JSON file is written in the directory my_experiment of the archive. It contains the input data, output data and metadata. Two files identified at model-level as being persistent have also been copied from the scratch to the archive.

model.execute({"x1": atleast_1d(2.0), "x2": atleast_1d(-2.0)})

Out:

    INFO - 16:51:57: Current root directory of scratch directory is /home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/scratch/visualize_model_result.
    INFO - 16:51:57: Current root directory of job directory is /home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/archive/visualize_model_result.

{'x1': array([2.]), 'x2': array([-2.]), 'x3': array([1., 2., 3.]), 'y1': 0.0, 'y2': array([1, 2, 3, 4, 5]), 'y3': array(['mock_string'], dtype='<U11'), 'y4': array([6.]), 'y5': array([  6.,  36., 216.]), 'error_code': array([0]), 'model': array(['MockModelPersistent'], dtype='<U19'), 'load_case': array(['LC1'], dtype='<U3'), 'description': array([''], dtype='<U1'), 'job_name': array([''], dtype='<U1'), 'persistent_result_files': array(['green_ellipse.png', 'blue_line.png'], dtype='<U17'), 'n_cpus': array([1]), 'date': array(['2026-05-11 16:51:57.376778'], dtype='<U26'), 'cpu_time': array([0.00463486]), 'user': array(['sebastien.bocquet'], dtype='<U17'), 'machine': array(['IPF7101'], dtype='<U7'), 'vims_git_version': array(['45528c25944ac4bb00d0644363b39dcb47cf0899'], dtype='<U40'), 'directory_archive_root': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/archive/visualize_model_result'],
      dtype='<U111'), 'directory_archive_job': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/archive/visualize_model_result/MockModelPersistent/LC1/4'],
      dtype='<U137'), 'directory_scratch_root': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/scratch/visualize_model_result'],
      dtype='<U111'), 'directory_scratch_job': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/scratch/visualize_model_result/MockModelPersistent/LC1/4'],
      dtype='<U137')}

It often occurs that a cache becomes unusable due to: - a change of VIMSEO version, altering the input, output or metadata of a model, - a mix of successful and failed simulations (by fail, we mean that models may be fault tolerant and outputs NaNs in the output data in case of errors). In general, we want to filter out failed runs. - a large number of results, and for part of them, we have no traceability about and we do not trust the results. Then, a useful feature is to generate a cache file from an archive. create_cache_from_archive() generates a cache file with same name as the current cache suffixed by '_from_archive'. For a DirectoryArchive, the whole archive is considered (no filtering possible):

cache_from_archive = model.create_cache_from_archive()
print(cache_from_archive)

Out:

Name: node
   Type: HDF5Cache
   Tolerance: 1e-10
   Input names: ['x1', 'x2', 'x3']
   Output names: ['cpu_time', 'date', 'description', 'directory_archive_job', 'directory_archive_root', 'directory_scratch_job', 'directory_scratch_root', 'error_code', 'job_name', 'load_case', 'machine', 'model', 'n_cpus', 'persistent_result_files', 'user', 'vims_git_version', 'y1', 'y2', 'y3', 'y4', 'y5']
   Length: 1
   HDF file path: /home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/caches/visualize_model_result/MockModelPersistent_LC1_from_archive.hdf
   HDF node path: node

An archive storage based on MLflow is also available. MLflow stores the runs by so-called 'Experiments'. The default experiment name is {model_name}_{load_case}. Again, we create a model instance:

model_name = "BendingTestAnalytical"
load_case = "Cantilever"
model = create_model(
    model_name,
    load_case,
    model_options=IntegratedModelSettings(
        directory_archive_root=EXAMPLE_RUNS_DIR
        / "mlflow_archive/visualize_model_result",
        directory_scratch_root=EXAMPLE_RUNS_DIR / "scratch/visualize_model_result",
        cache_file_path=EXAMPLE_RUNS_DIR
        / f"caches/visualize_model_result/{model_name}_{load_case}_cache.hdf",
        archive_manager="MlflowArchive",
    ),
)
model.cache = None

All runs for the default and specific (specified below as my_experiment) experiment name are first deleted:

runs = mlflow.search_runs(
    experiment_names=["BendingTestAnalytical_Cantilever", "my_experiment"]
)
for id in runs["run_id"]:
    delete_run(id)

# A first execution is done. Its result is stored under the default experiment name,
# which is ``{model_name}_{load_case}``:
model.execute({"height": atleast_1d(40.0)})

Out:

    INFO - 16:51:57: Current root directory of job directory is file:////home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result.

{'length': array([600.]), 'width': array([30.]), 'height': array([40.]), 'imposed_dplt': array([-5.]), 'relative_dplt_location': array([1.]), 'young_modulus': array([210000.]), 'nu_p': array([0.3]), 'reaction_forces': array([-2333.33333333]), 'moment': array([1400000.,       0.]), 'moment_grid': array([  0., 600.]), 'dplt': array([ 0.00000000e+00, -7.62651513e-04, -3.04029995e-03, -6.81748616e-03,
       -1.20787510e-02, -1.88086353e-02, -2.69916799e-02, -3.66124257e-02,
       -4.76554134e-02, -6.01051841e-02, -7.39462784e-02, -8.91632373e-02,
       -1.05740602e-01, -1.23662912e-01, -1.42914710e-01, -1.63480535e-01,
       -1.85344930e-01, -2.08492434e-01, -2.32907588e-01, -2.58574934e-01,
       -2.85479012e-01, -3.13604363e-01, -3.42935528e-01, -3.73457048e-01,
       -4.05153463e-01, -4.38009315e-01, -4.72009144e-01, -5.07137491e-01,
       -5.43378897e-01, -5.80717902e-01, -6.19139049e-01, -6.58626877e-01,
       -6.99165927e-01, -7.40740741e-01, -7.83335858e-01, -8.26935821e-01,
       -8.71525169e-01, -9.17088444e-01, -9.63610186e-01, -1.01107494e+00,
       -1.05946724e+00, -1.10877163e+00, -1.15897265e+00, -1.21005484e+00,
       -1.26200274e+00, -1.31480090e+00, -1.36843385e+00, -1.42288614e+00,
       -1.47814230e+00, -1.53418688e+00, -1.59100442e+00, -1.64857946e+00,
       -1.70689653e+00, -1.76594019e+00, -1.82569497e+00, -1.88614540e+00,
       -1.94727605e+00, -2.00907143e+00, -2.07151610e+00, -2.13459459e+00,
       -2.19829145e+00, -2.26259122e+00, -2.32747844e+00, -2.39293764e+00,
       -2.45895337e+00, -2.52551018e+00, -2.59259259e+00, -2.66018516e+00,
       -2.72827242e+00, -2.79683891e+00, -2.86586918e+00, -2.93534776e+00,
       -3.00525920e+00, -3.07558804e+00, -3.14631882e+00, -3.21743607e+00,
       -3.28892434e+00, -3.36076818e+00, -3.43295211e+00, -3.50546069e+00,
       -3.57827845e+00, -3.65138993e+00, -3.72477968e+00, -3.79843224e+00,
       -3.87233214e+00, -3.94646393e+00, -4.02081214e+00, -4.09536133e+00,
       -4.17009602e+00, -4.24500077e+00, -4.32006011e+00, -4.39525857e+00,
       -4.47058072e+00, -4.54601107e+00, -4.62153419e+00, -4.69713459e+00,
       -4.77279684e+00, -4.84850546e+00, -4.92424500e+00, -5.00000000e+00]), 'dplt_grid': array([  0.        ,   6.06060606,  12.12121212,  18.18181818,
        24.24242424,  30.3030303 ,  36.36363636,  42.42424242,
        48.48484848,  54.54545455,  60.60606061,  66.66666667,
        72.72727273,  78.78787879,  84.84848485,  90.90909091,
        96.96969697, 103.03030303, 109.09090909, 115.15151515,
       121.21212121, 127.27272727, 133.33333333, 139.39393939,
       145.45454545, 151.51515152, 157.57575758, 163.63636364,
       169.6969697 , 175.75757576, 181.81818182, 187.87878788,
       193.93939394, 200.        , 206.06060606, 212.12121212,
       218.18181818, 224.24242424, 230.3030303 , 236.36363636,
       242.42424242, 248.48484848, 254.54545455, 260.60606061,
       266.66666667, 272.72727273, 278.78787879, 284.84848485,
       290.90909091, 296.96969697, 303.03030303, 309.09090909,
       315.15151515, 321.21212121, 327.27272727, 333.33333333,
       339.39393939, 345.45454545, 351.51515152, 357.57575758,
       363.63636364, 369.6969697 , 375.75757576, 381.81818182,
       387.87878788, 393.93939394, 400.        , 406.06060606,
       412.12121212, 418.18181818, 424.24242424, 430.3030303 ,
       436.36363636, 442.42424242, 448.48484848, 454.54545455,
       460.60606061, 466.66666667, 472.72727273, 478.78787879,
       484.84848485, 490.90909091, 496.96969697, 503.03030303,
       509.09090909, 515.15151515, 521.21212121, 527.27272727,
       533.33333333, 539.39393939, 545.45454545, 551.51515152,
       557.57575758, 563.63636364, 569.6969697 , 575.75757576,
       581.81818182, 587.87878788, 593.93939394, 600.        ]), 'dplt_at_force_location': array([-5.]), 'maximum_dplt': array([-5.]), 'location_max_dplt': array([600.]), 'error_code': array([0]), 'model': array(['BendingTestAnalytical'], dtype='<U21'), 'load_case': array(['Cantilever'], dtype='<U10'), 'description': array([''], dtype='<U1'), 'job_name': array([''], dtype='<U1'), 'persistent_result_files': array([''], dtype='<U1'), 'n_cpus': array([1]), 'date': array(['2026-05-11 16:51:57.470198'], dtype='<U26'), 'cpu_time': array([0.01803923]), 'user': array(['sebastien.bocquet'], dtype='<U17'), 'machine': array(['IPF7101'], dtype='<U7'), 'vims_git_version': array(['45528c25944ac4bb00d0644363b39dcb47cf0899'], dtype='<U40'), 'directory_archive_root': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result'],
      dtype='<U118'), 'directory_archive_job': array([''], dtype='<U1'), 'directory_scratch_root': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/scratch/visualize_model_result'],
      dtype='<U111'), 'directory_scratch_job': array([''], dtype='<U1')}

Then, another experiment is defined. It can be useful to group runs for a given study. The model is then executed for two different beam height:

model.archive_manager.set_experiment("my_experiment")
model.execute({"height": atleast_1d(50.0)})
model.execute({"height": atleast_1d(60.0)})

Out:

    INFO - 16:51:57: Current root directory of job directory is file:////home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result.
    INFO - 16:51:57: Current root directory of job directory is file:////home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result.

{'length': array([600.]), 'width': array([30.]), 'height': array([60.]), 'imposed_dplt': array([-5.]), 'relative_dplt_location': array([1.]), 'young_modulus': array([210000.]), 'nu_p': array([0.3]), 'reaction_forces': array([-7875.]), 'moment': array([4725000.,       0.]), 'moment_grid': array([  0., 600.]), 'dplt': array([ 0.00000000e+00, -7.62651513e-04, -3.04029995e-03, -6.81748616e-03,
       -1.20787510e-02, -1.88086353e-02, -2.69916799e-02, -3.66124257e-02,
       -4.76554134e-02, -6.01051841e-02, -7.39462784e-02, -8.91632373e-02,
       -1.05740602e-01, -1.23662912e-01, -1.42914710e-01, -1.63480535e-01,
       -1.85344930e-01, -2.08492434e-01, -2.32907588e-01, -2.58574934e-01,
       -2.85479012e-01, -3.13604363e-01, -3.42935528e-01, -3.73457048e-01,
       -4.05153463e-01, -4.38009315e-01, -4.72009144e-01, -5.07137491e-01,
       -5.43378897e-01, -5.80717902e-01, -6.19139049e-01, -6.58626877e-01,
       -6.99165927e-01, -7.40740741e-01, -7.83335858e-01, -8.26935821e-01,
       -8.71525169e-01, -9.17088444e-01, -9.63610186e-01, -1.01107494e+00,
       -1.05946724e+00, -1.10877163e+00, -1.15897265e+00, -1.21005484e+00,
       -1.26200274e+00, -1.31480090e+00, -1.36843385e+00, -1.42288614e+00,
       -1.47814230e+00, -1.53418688e+00, -1.59100442e+00, -1.64857946e+00,
       -1.70689653e+00, -1.76594019e+00, -1.82569497e+00, -1.88614540e+00,
       -1.94727605e+00, -2.00907143e+00, -2.07151610e+00, -2.13459459e+00,
       -2.19829145e+00, -2.26259122e+00, -2.32747844e+00, -2.39293764e+00,
       -2.45895337e+00, -2.52551018e+00, -2.59259259e+00, -2.66018516e+00,
       -2.72827242e+00, -2.79683891e+00, -2.86586918e+00, -2.93534776e+00,
       -3.00525920e+00, -3.07558804e+00, -3.14631882e+00, -3.21743607e+00,
       -3.28892434e+00, -3.36076818e+00, -3.43295211e+00, -3.50546069e+00,
       -3.57827845e+00, -3.65138993e+00, -3.72477968e+00, -3.79843224e+00,
       -3.87233214e+00, -3.94646393e+00, -4.02081214e+00, -4.09536133e+00,
       -4.17009602e+00, -4.24500077e+00, -4.32006011e+00, -4.39525857e+00,
       -4.47058072e+00, -4.54601107e+00, -4.62153419e+00, -4.69713459e+00,
       -4.77279684e+00, -4.84850546e+00, -4.92424500e+00, -5.00000000e+00]), 'dplt_grid': array([  0.        ,   6.06060606,  12.12121212,  18.18181818,
        24.24242424,  30.3030303 ,  36.36363636,  42.42424242,
        48.48484848,  54.54545455,  60.60606061,  66.66666667,
        72.72727273,  78.78787879,  84.84848485,  90.90909091,
        96.96969697, 103.03030303, 109.09090909, 115.15151515,
       121.21212121, 127.27272727, 133.33333333, 139.39393939,
       145.45454545, 151.51515152, 157.57575758, 163.63636364,
       169.6969697 , 175.75757576, 181.81818182, 187.87878788,
       193.93939394, 200.        , 206.06060606, 212.12121212,
       218.18181818, 224.24242424, 230.3030303 , 236.36363636,
       242.42424242, 248.48484848, 254.54545455, 260.60606061,
       266.66666667, 272.72727273, 278.78787879, 284.84848485,
       290.90909091, 296.96969697, 303.03030303, 309.09090909,
       315.15151515, 321.21212121, 327.27272727, 333.33333333,
       339.39393939, 345.45454545, 351.51515152, 357.57575758,
       363.63636364, 369.6969697 , 375.75757576, 381.81818182,
       387.87878788, 393.93939394, 400.        , 406.06060606,
       412.12121212, 418.18181818, 424.24242424, 430.3030303 ,
       436.36363636, 442.42424242, 448.48484848, 454.54545455,
       460.60606061, 466.66666667, 472.72727273, 478.78787879,
       484.84848485, 490.90909091, 496.96969697, 503.03030303,
       509.09090909, 515.15151515, 521.21212121, 527.27272727,
       533.33333333, 539.39393939, 545.45454545, 551.51515152,
       557.57575758, 563.63636364, 569.6969697 , 575.75757576,
       581.81818182, 587.87878788, 593.93939394, 600.        ]), 'dplt_at_force_location': array([-5.]), 'maximum_dplt': array([-5.]), 'location_max_dplt': array([600.]), 'error_code': array([0]), 'model': array(['BendingTestAnalytical'], dtype='<U21'), 'load_case': array(['Cantilever'], dtype='<U10'), 'description': array([''], dtype='<U1'), 'job_name': array([''], dtype='<U1'), 'persistent_result_files': array([''], dtype='<U1'), 'n_cpus': array([1]), 'date': array(['2026-05-11 16:51:57.838037'], dtype='<U26'), 'cpu_time': array([0.00309539]), 'user': array(['sebastien.bocquet'], dtype='<U17'), 'machine': array(['IPF7101'], dtype='<U7'), 'vims_git_version': array(['45528c25944ac4bb00d0644363b39dcb47cf0899'], dtype='<U40'), 'directory_archive_root': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result'],
      dtype='<U118'), 'directory_archive_job': array([''], dtype='<U1'), 'directory_scratch_root': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/scratch/visualize_model_result'],
      dtype='<U111'), 'directory_scratch_job': array([''], dtype='<U1')}

The archive results can be visualized through the MLflow User Interface. The uri of the MLflow database must be specified to the UI: - For exemple, Under Windows: mlflow ui --backend-store-uri file:\\\\\\C:\\Users\\sebastien.bocquet\\PycharmProjects\\vimseo\\tests\\storage_management\\my_experiment - Or under Linux: mlflow ui --backend-store-uri file:////home/sebastien.bocquet/PycharmProjects/vims_only/doc_src/_examples/02-integrated_models/mlflow_archive The uri can be retrieved with model._storage_manager.uri.

The mapping from model raw results (input, output, metadata) and MLflow result tracking framework is done as follows: - all numbers among the input and output data (including arrays of size one, from which the number is extracted), are stored as MLflow metrics. Inputs are distinguished by prefixing their name by inputs., - all other type of data among the input and output data are considered as MLflow params. They are jsonified and stored as strings, - the metadata are stored as MLflow tags. The UI segregates the runs by experiment, and provides a searching functionality. MLflow is compatible with several databases (remote PostgreSql, Amazon S3 etc...), allowing the archive storage to scale (company level or project level).

Runs can also be searched programmatically, here by experiment and for a given input range.

runs = mlflow.search_runs(
    experiment_names=["my_experiment"],
    filter_string="metrics.inputs.height > 50.0",
)
assert len(runs) == 1
runs
run_id experiment_id status artifact_uri start_time end_time metrics.dplt_grid metrics.inputs.young_modulus metrics.cpu_time metrics.inputs.imposed_dplt metrics.inputs.width metrics.moment metrics.moment_grid metrics.location_max_dplt metrics.maximum_dplt metrics.inputs.height metrics.inputs.relative_dplt_location metrics.dplt metrics.inputs.length metrics.reaction_forces metrics.inputs.nu_p metrics.dplt_at_force_location params.dplt_grid params.dplt params.moment params.moment_grid tags.persistent_result_files tags.error_code tags.model tags.description tags.directory_scratch_job tags.n_cpus tags.mlflow.source.name tags.mlflow.source.git.commit tags.mlflow.source.type tags.date tags.user tags.directory_archive_root tags.machine tags.mlflow.runName tags.mlflow.user tags.vims_git_version tags.job_name tags.directory_scratch_root tags.directory_archive_job tags.load_case
0 2d3e9221b5eb4abaaac459e6972b287f 364922672964428165 FINISHED file://home/sebastien.bocquet/PycharmProjects/... 2026-05-11 14:51:57.839000+00:00 2026-05-11 14:51:58.007000+00:00 600.0 210000.0 0.003095 -5.0 30.0 0.0 600.0 600.0 -5.0 60.0 1.0 -5.0 600.0 -7875.0 0.3 -5.0 [0.0, 6.0606060606060606, 12.121212121212121, ... [0.0, -0.0007626515125749891, -0.0030402999487... [4725000.0, 0.0] [0.0, 600.0] "" 0 BendingTestAnalytical 1 /home/sebastien.bocquet/PycharmProjects/vimseo... 45528c25944ac4bb00d0644363b39dcb47cf0899 LOCAL 2026-05-11 16:51:57.838037 sebastien.bocquet /home/sebastien.bocquet/PycharmProjects/vimseo... IPF7101 placid-bass-492 sebastien.bocquet 45528c25944ac4bb00d0644363b39dcb47cf0899 /home/sebastien.bocquet/PycharmProjects/vimseo... Cantilever

Metadata could also be used in the query, for instance tags.user = "a_user". Note that the result returned by get_result() has the following format: {"inputs": input_data, "output": output_data}.

archive_result = model.archive_manager.get_result(runs["run_id"][0])
archive_result

Out:

{'inputs': {'length': array([600.]), 'width': array([30.]), 'height': array([60.]), 'imposed_dplt': array([-5.]), 'relative_dplt_location': array([1.]), 'young_modulus': array([210000.]), 'nu_p': array([0.3])}, 'outputs': {'reaction_forces': array([-7875.]), 'dplt_at_force_location': array([-5.]), 'maximum_dplt': array([-5.]), 'location_max_dplt': array([600.]), 'cpu_time': array([0.00309539]), 'moment': array([4725000.,       0.]), 'moment_grid': array([  0., 600.]), 'dplt': array([ 0.00000000e+00, -7.62651513e-04, -3.04029995e-03, -6.81748616e-03,
       -1.20787510e-02, -1.88086353e-02, -2.69916799e-02, -3.66124257e-02,
       -4.76554134e-02, -6.01051841e-02, -7.39462784e-02, -8.91632373e-02,
       -1.05740602e-01, -1.23662912e-01, -1.42914710e-01, -1.63480535e-01,
       -1.85344930e-01, -2.08492434e-01, -2.32907588e-01, -2.58574934e-01,
       -2.85479012e-01, -3.13604363e-01, -3.42935528e-01, -3.73457048e-01,
       -4.05153463e-01, -4.38009315e-01, -4.72009144e-01, -5.07137491e-01,
       -5.43378897e-01, -5.80717902e-01, -6.19139049e-01, -6.58626877e-01,
       -6.99165927e-01, -7.40740741e-01, -7.83335858e-01, -8.26935821e-01,
       -8.71525169e-01, -9.17088444e-01, -9.63610186e-01, -1.01107494e+00,
       -1.05946724e+00, -1.10877163e+00, -1.15897265e+00, -1.21005484e+00,
       -1.26200274e+00, -1.31480090e+00, -1.36843385e+00, -1.42288614e+00,
       -1.47814230e+00, -1.53418688e+00, -1.59100442e+00, -1.64857946e+00,
       -1.70689653e+00, -1.76594019e+00, -1.82569497e+00, -1.88614540e+00,
       -1.94727605e+00, -2.00907143e+00, -2.07151610e+00, -2.13459459e+00,
       -2.19829145e+00, -2.26259122e+00, -2.32747844e+00, -2.39293764e+00,
       -2.45895337e+00, -2.52551018e+00, -2.59259259e+00, -2.66018516e+00,
       -2.72827242e+00, -2.79683891e+00, -2.86586918e+00, -2.93534776e+00,
       -3.00525920e+00, -3.07558804e+00, -3.14631882e+00, -3.21743607e+00,
       -3.28892434e+00, -3.36076818e+00, -3.43295211e+00, -3.50546069e+00,
       -3.57827845e+00, -3.65138993e+00, -3.72477968e+00, -3.79843224e+00,
       -3.87233214e+00, -3.94646393e+00, -4.02081214e+00, -4.09536133e+00,
       -4.17009602e+00, -4.24500077e+00, -4.32006011e+00, -4.39525857e+00,
       -4.47058072e+00, -4.54601107e+00, -4.62153419e+00, -4.69713459e+00,
       -4.77279684e+00, -4.84850546e+00, -4.92424500e+00, -5.00000000e+00]), 'dplt_grid': array([  0.        ,   6.06060606,  12.12121212,  18.18181818,
        24.24242424,  30.3030303 ,  36.36363636,  42.42424242,
        48.48484848,  54.54545455,  60.60606061,  66.66666667,
        72.72727273,  78.78787879,  84.84848485,  90.90909091,
        96.96969697, 103.03030303, 109.09090909, 115.15151515,
       121.21212121, 127.27272727, 133.33333333, 139.39393939,
       145.45454545, 151.51515152, 157.57575758, 163.63636364,
       169.6969697 , 175.75757576, 181.81818182, 187.87878788,
       193.93939394, 200.        , 206.06060606, 212.12121212,
       218.18181818, 224.24242424, 230.3030303 , 236.36363636,
       242.42424242, 248.48484848, 254.54545455, 260.60606061,
       266.66666667, 272.72727273, 278.78787879, 284.84848485,
       290.90909091, 296.96969697, 303.03030303, 309.09090909,
       315.15151515, 321.21212121, 327.27272727, 333.33333333,
       339.39393939, 345.45454545, 351.51515152, 357.57575758,
       363.63636364, 369.6969697 , 375.75757576, 381.81818182,
       387.87878788, 393.93939394, 400.        , 406.06060606,
       412.12121212, 418.18181818, 424.24242424, 430.3030303 ,
       436.36363636, 442.42424242, 448.48484848, 454.54545455,
       460.60606061, 466.66666667, 472.72727273, 478.78787879,
       484.84848485, 490.90909091, 496.96969697, 503.03030303,
       509.09090909, 515.15151515, 521.21212121, 527.27272727,
       533.33333333, 539.39393939, 545.45454545, 551.51515152,
       557.57575758, 563.63636364, 569.6969697 , 575.75757576,
       581.81818182, 587.87878788, 593.93939394, 600.        ]), 'error_code': array([0]), 'model': array(['BendingTestAnalytical'], dtype='<U21'), 'load_case': array(['Cantilever'], dtype='<U10'), 'description': array([''], dtype='<U1'), 'job_name': array([''], dtype='<U1'), 'n_cpus': array([1]), 'date': array(['2026-05-11 16:51:57.838037'], dtype='<U26'), 'user': array(['sebastien.bocquet'], dtype='<U17'), 'machine': array(['IPF7101'], dtype='<U7'), 'vims_git_version': array(['45528c25944ac4bb00d0644363b39dcb47cf0899'], dtype='<U40'), 'directory_archive_root': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result'],
      dtype='<U118'), 'directory_archive_job': array(['/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result/364922672964428165/2d3e9221b5eb4abaaac459e6972b287f/artifacts'],
      dtype='<U175'), 'directory_scratch_root': array(['/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/scratch/visualize_model_result'],
      dtype='<U111'), 'directory_scratch_job': array([''], dtype='<U1'), <MetaDataNames.persistent_result_files: 'persistent_result_files'>: array([''], dtype='<U1')}}

It can be converted to a user-friendly result format ModelResult that can be easily visualized and compared:

result = ModelResult.from_data(archive_result)
result

Out:

    INFO - 16:51:58: Found 1 entries in the cache file : BendingTestAnalytical_Cantilever_cache.hdf node : node

ModelResult(metadata=ToolResultMetadata(generic={'datetime': '11-05-2026_16-51-58', 'version': '0.1.7.dev11+g45528c259'}, misc={}, settings={}, report={<MetaDataNames.model: 'model'>: 'BendingTestAnalytical', <MetaDataNames.load_case: 'load_case'>: 'Cantilever', <MetaDataNames.error_code: 'error_code'>: 0, <MetaDataNames.description: 'description'>: '', <MetaDataNames.job_name: 'job_name'>: '', <MetaDataNames.persistent_result_files: 'persistent_result_files'>: '', <MetaDataNames.n_cpus: 'n_cpus'>: 1, <MetaDataNames.date: 'date'>: '2026-05-11 16:51:57.838037', <MetaDataNames.cpu_time: 'cpu_time'>: 0.003095388412475586, <MetaDataNames.user: 'user'>: 'sebastien.bocquet', <MetaDataNames.machine: 'machine'>: 'IPF7101', <MetaDataNames.vims_git_version: 'vims_git_version'>: '45528c25944ac4bb00d0644363b39dcb47cf0899', <MetaDataNames.directory_archive_root: 'directory_archive_root'>: '/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result', <MetaDataNames.directory_archive_job: 'directory_archive_job'>: '/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result/364922672964428165/2d3e9221b5eb4abaaac459e6972b287f/artifacts', <MetaDataNames.directory_scratch_root: 'directory_scratch_root'>: '/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/scratch/visualize_model_result', <MetaDataNames.directory_scratch_job: 'directory_scratch_job'>: ''}, model=ModelDescription(name='BendingTestAnalytical', summary=' An analytical model for the bending of a parallelepipedic beam', load_case=Beam_Cantilever(name='Cantilever', domain='Beam', summary='A cantilever load case.', plot_parameters=PlotParameters(curves=[]), bc_variable_names=['imposed_dplt', 'relative_dplt_location'], load=Load(direction='', sign='', type='')), dataflow={'model_inputs': ['length', 'width', 'height', 'imposed_dplt', 'relative_dplt_location', 'young_modulus', 'nu_p'], 'model_outputs': ['reaction_forces', 'maximum_dplt', 'dplt_grid', 'location_max_dplt', 'dplt', 'moment', 'moment_grid', 'dplt_at_force_location', 'error_code', 'model', 'load_case', 'description', 'job_name', 'persistent_result_files', 'n_cpus', 'date', 'cpu_time', 'user', 'machine', 'vims_git_version', 'directory_archive_root', 'directory_archive_job', 'directory_scratch_root', 'directory_scratch_job'], 'PreBendingTestAnalytical_Cantilever': {'inputs': ['length', 'width', 'height', 'imposed_dplt', 'relative_dplt_location', 'young_modulus', 'nu_p'], 'outputs': ['imposed_dplt_location', 'quadratic_moment', 'reaction_forces', 'moment', 'moment_grid', 'solver', 'boundary']}, 'RunBendingTestAnalytical': {'inputs': ['imposed_dplt_location', 'quadratic_moment', 'reaction_forces', 'moment', 'moment_grid', 'solver', 'boundary', 'length', 'width', 'height', 'imposed_dplt', 'relative_dplt_location', 'young_modulus', 'nu_p'], 'outputs': ['dplt', 'dplt_grid', 'moment', 'moment_grid', 'imposed_dplt_location', 'reaction_forces']}, 'PostBendingTestAnalytical_Cantilever': {'inputs': ['dplt', 'dplt_grid', 'moment', 'moment_grid', 'imposed_dplt_location', 'reaction_forces'], 'outputs': ['reaction_forces', 'maximum_dplt', 'dplt_grid', 'location_max_dplt', 'dplt', 'moment', 'moment_grid', 'dplt_at_force_location', 'error_code']}, 'subroutine_names': []}, default_inputs={<InputGroupNames.NUMERICAL_VARS: 'numerical variables'>: {}, <InputGroupNames.BC_VARS: 'boundary conditions variables'>: {'imposed_dplt': [-5.0], 'relative_dplt_location': [1.0]}, <InputGroupNames.GEOMETRICAL_VARS: 'geometrical variables'>: {'length': [600.0], 'width': [30.0], 'height': [40.0]}, <InputGroupNames.MATERIAL_VARS: 'material variables'>: {'young_modulus': [210000.0], 'nu_p': [0.3]}}, curves=[('dplt_grid', 'dplt'), ('moment_grid', 'moment')], verbose=False)), scalars={'length': 600.0, 'width': 30.0, 'height': 60.0, 'imposed_dplt': -5.0, 'relative_dplt_location': 1.0, 'young_modulus': 210000.0, 'nu_p': 0.3, 'reaction_forces': -7875.0, 'dplt_at_force_location': -5.0000000000000036, 'maximum_dplt': -5.0000000000000036, 'location_max_dplt': 600.0}, vectors={}, curves=[<vimseo.utilities.curves.Curve object at 0x7f27cf8f7a10>, <vimseo.utilities.curves.Curve object at 0x7f27cf7ada10>], fields={}, snapshots=[])

We perform another query to retrieve the second run:

runs = mlflow.search_runs(
    experiment_names=["my_experiment"],
    filter_string="metrics.inputs.height <= 51.0 AND metrics.inputs.height >= 49.0",
)
assert len(runs) == 1
archive_result_1 = model.archive_manager.get_result(runs["run_id"][0])
result_1 = ModelResult.from_data(archive_result_1)
result_1

Out:

    INFO - 16:51:58: Found 1 entries in the cache file : BendingTestAnalytical_Cantilever_cache.hdf node : node

ModelResult(metadata=ToolResultMetadata(generic={'datetime': '11-05-2026_16-51-58', 'version': '0.1.7.dev11+g45528c259'}, misc={}, settings={}, report={<MetaDataNames.model: 'model'>: 'BendingTestAnalytical', <MetaDataNames.load_case: 'load_case'>: 'Cantilever', <MetaDataNames.error_code: 'error_code'>: 0, <MetaDataNames.description: 'description'>: '', <MetaDataNames.job_name: 'job_name'>: '', <MetaDataNames.persistent_result_files: 'persistent_result_files'>: '', <MetaDataNames.n_cpus: 'n_cpus'>: 1, <MetaDataNames.date: 'date'>: '2026-05-11 16:51:57.677753', <MetaDataNames.cpu_time: 'cpu_time'>: 0.0028858184814453125, <MetaDataNames.user: 'user'>: 'sebastien.bocquet', <MetaDataNames.machine: 'machine'>: 'IPF7101', <MetaDataNames.vims_git_version: 'vims_git_version'>: '45528c25944ac4bb00d0644363b39dcb47cf0899', <MetaDataNames.directory_archive_root: 'directory_archive_root'>: '/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result', <MetaDataNames.directory_archive_job: 'directory_archive_job'>: '/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/mlflow_archive/visualize_model_result/364922672964428165/37861ae19494438fbf73afea4488fccd/artifacts', <MetaDataNames.directory_scratch_root: 'directory_scratch_root'>: '/home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/scratch/visualize_model_result', <MetaDataNames.directory_scratch_job: 'directory_scratch_job'>: ''}, model=ModelDescription(name='BendingTestAnalytical', summary=' An analytical model for the bending of a parallelepipedic beam', load_case=Beam_Cantilever(name='Cantilever', domain='Beam', summary='A cantilever load case.', plot_parameters=PlotParameters(curves=[]), bc_variable_names=['imposed_dplt', 'relative_dplt_location'], load=Load(direction='', sign='', type='')), dataflow={'model_inputs': ['length', 'width', 'height', 'imposed_dplt', 'relative_dplt_location', 'young_modulus', 'nu_p'], 'model_outputs': ['reaction_forces', 'maximum_dplt', 'dplt_grid', 'location_max_dplt', 'dplt', 'moment', 'moment_grid', 'dplt_at_force_location', 'error_code', 'model', 'load_case', 'description', 'job_name', 'persistent_result_files', 'n_cpus', 'date', 'cpu_time', 'user', 'machine', 'vims_git_version', 'directory_archive_root', 'directory_archive_job', 'directory_scratch_root', 'directory_scratch_job'], 'PreBendingTestAnalytical_Cantilever': {'inputs': ['length', 'width', 'height', 'imposed_dplt', 'relative_dplt_location', 'young_modulus', 'nu_p'], 'outputs': ['imposed_dplt_location', 'quadratic_moment', 'reaction_forces', 'moment', 'moment_grid', 'solver', 'boundary']}, 'RunBendingTestAnalytical': {'inputs': ['imposed_dplt_location', 'quadratic_moment', 'reaction_forces', 'moment', 'moment_grid', 'solver', 'boundary', 'length', 'width', 'height', 'imposed_dplt', 'relative_dplt_location', 'young_modulus', 'nu_p'], 'outputs': ['dplt', 'dplt_grid', 'moment', 'moment_grid', 'imposed_dplt_location', 'reaction_forces']}, 'PostBendingTestAnalytical_Cantilever': {'inputs': ['dplt', 'dplt_grid', 'moment', 'moment_grid', 'imposed_dplt_location', 'reaction_forces'], 'outputs': ['reaction_forces', 'maximum_dplt', 'dplt_grid', 'location_max_dplt', 'dplt', 'moment', 'moment_grid', 'dplt_at_force_location', 'error_code']}, 'subroutine_names': []}, default_inputs={<InputGroupNames.NUMERICAL_VARS: 'numerical variables'>: {}, <InputGroupNames.BC_VARS: 'boundary conditions variables'>: {'imposed_dplt': [-5.0], 'relative_dplt_location': [1.0]}, <InputGroupNames.GEOMETRICAL_VARS: 'geometrical variables'>: {'length': [600.0], 'width': [30.0], 'height': [40.0]}, <InputGroupNames.MATERIAL_VARS: 'material variables'>: {'young_modulus': [210000.0], 'nu_p': [0.3]}}, curves=[('dplt_grid', 'dplt'), ('moment_grid', 'moment')], verbose=False)), scalars={'length': 600.0, 'width': 30.0, 'height': 50.0, 'imposed_dplt': -5.0, 'relative_dplt_location': 1.0, 'young_modulus': 210000.0, 'nu_p': 0.3, 'reaction_forces': -4557.291666666667, 'dplt_at_force_location': -5.000000000000002, 'maximum_dplt': -5.000000000000002, 'location_max_dplt': 600.0}, vectors={}, curves=[<vimseo.utilities.curves.Curve object at 0x7f27cf8a9250>, <vimseo.utilities.curves.Curve object at 0x7f27cf90d1d0>], fields={}, snapshots=[])

It is possible to create a cache file from the archive. By default, the runs of the current experiment are considered. Since experiment "my_experiment" has two runs, we expect the created cache to have two entries:

cache_from_archive = model.create_cache_from_archive()
print(cache_from_archive)

Out:

Name: node
   Type: HDF5Cache
   Tolerance: 1e-10
   Input names: ['height', 'imposed_dplt', 'length', 'nu_p', 'relative_dplt_location', 'width', 'young_modulus']
   Output names: ['cpu_time', 'date', 'description', 'directory_archive_job', 'directory_archive_root', 'directory_scratch_job', 'directory_scratch_root', 'dplt', 'dplt_at_force_location', 'dplt_grid', 'error_code', 'job_name', 'load_case', 'location_max_dplt', 'machine', 'maximum_dplt', 'model', 'moment', 'moment_grid', 'n_cpus', 'persistent_result_files', 'reaction_forces', 'user', 'vims_git_version']
   Length: 2
   HDF file path: /home/sebastien.bocquet/PycharmProjects/vimseo/docs/runnable_examples/model_runs/caches/visualize_model_result/BendingTestAnalytical_Cantilever_from_archive.hdf
   HDF node path: node

The displacement curves of both results can be compared.They are identical for this displacement-imposed loading:

plot_curves(
    [
        result.get_curve(("dplt_grid", "dplt")),
        result_1.get_curve(("dplt_grid", "dplt")),
    ],
    labels=["result", "result 1"],
    save=False,
    show=True,
)

However the resistance force at beam end are different:

variable_names = ["height", "reaction_forces"]
df = concat(
    [
        DataFrame([result.get_numeric_scalars(variable_names=variable_names)]),
        DataFrame([result_1.get_numeric_scalars(variable_names=variable_names)]),
    ],
    ignore_index=True,
)
plot = BarPlot(Dataset.from_dataframe(df))
plot.title = "Comparison of model result with data"
plot.font_size = 20
plot.labels = ["height 50mm", "height 60mm"]
fig = plot.execute(save=False, show=True, file_format="html")[0]
fig

Total running time of the script: ( 0 minutes 1.787 seconds)

Download Python source code: plot_03_model_result_management.py

Download Jupyter notebook: plot_03_model_result_management.ipynb

Gallery generated by mkdocs-gallery