Note
Go to the end to download the full example code or to run this example in your browser via Binder.
Saving and Loading Models - The Storage System#
OGSTools provides a unified storage system for managing simulation artifacts.
This guide uses Model for all examples, but the same
principles and methods apply to all storage-capable objects. All inherit from
StorageBase and share the same storage interface.
The examples start with basic operations and progress to advanced features.
import ogstools as ot
# Set up a temporary directory for our examples
work_dir = ot.definitions.temp_dir(prefix="storage", dir="examples")
ot.StorageBase.Userpath = work_dir
Basic Usage: Creating and Saving a Model#
Create and save a model in just a few lines.
# Create components
meshes = ot.Meshes.from_gmsh(ot.gmsh_tools.rect(n_edge_cells=10))
prj_path = ot.definitions.EXAMPLES_DIR / "prj" / "mechanics.prj"
project = ot.Project(input_file=prj_path)
# Create and save the model
my_model = ot.Model(project=project, meshes=meshes)
# Provide a path OR just .save()
my_model.save(work_dir / "my_model")
print(f"Model saved to: {my_model.active_target}")
Model saved to: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_model
Loading a Model#
Load a previously saved model using its path.
Model id: 20260601_131430_322181
Model(saved to: file:///tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_model)
Project: saved to file:///tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_model/project
Meshes: saved to file:///tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_model/meshes
Execution: saved to file:///tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_model/execution.yaml
Using IDs for Organization#
Alternatively, assign an ID to organize models in class-specific subdirectories. All objects with assigned id will be stored under ot.StorageBase.Userpath
model_experiment_1 = ot.Model(project=project, meshes=meshes, id="experiment_1")
model_experiment_1.save()
print(f"Model saved to: {model_experiment_1.active_target}")
# Load by ID
model_copy_experiment_1 = ot.Model.from_id("experiment_1")
print(f"Loaded by ID: {model_copy_experiment_1.active_target}")
Model saved to: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/Model/experiment_1
Loaded by ID: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/Model/experiment_1
Overwriting with Backup#
By default, overwriting requires explicit permission and creates backups. Change the behavior by changing ot.StorageBase.Backup=True/False
my_model.save(work_dir / "my_first_model", overwrite=True)
[PosixPath('/tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_first_model/.envrc')]
Archiving for Sharing#
Use archive=True to create self-contained copies (replaces symlinks with files).
my_model.save(work_dir / "archived", archive=True, overwrite=True)
[PosixPath('/tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/archived/.envrc')]
Roundtrip: Save and Load#
All storage classes support complete roundtrip (save → load → identical object). The __repr__ output shows how to reload objects:
print(my_model)
# Output shows: Model.from_folder('/path/to/repr_example')
Model id: 20260601_131430_212584
Model(saved to: file:///tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/archived)
Project: saved to file:///tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_model/project
Meshes: saved to file:///tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_model/meshes
Execution: saved to file:///tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/my_model/execution.yaml
Advanced Topics#
The following sections cover advanced storage concepts and internal behavior.
Storage Principles Overview#
The storage system follows these core principles:
Object-centric design: Work with Python objects; files are technical artifacts
Never overwrites originals: file → (copy) → object → new file (by default)
Cascading storage: Saving a parent (Model) automatically saves children
Deepcopy-aware: Copied objects get new, independent save targets
UserPath: Set
StorageBase.Userpathfor organized, ID-based storage
Inspecting Save Status#
Objects track their save state through several properties.
model_status = ot.Model(project=project, meshes=meshes)
print("Before saving:")
print(f" is_saved: {model_status.is_saved}")
print(f" active_target: {model_status.active_target}")
print(f" next_target: {model_status.next_target}")
model_status.save(work_dir / "status_example")
print("\nAfter saving:")
print(f" is_saved: {model_status.is_saved}")
print(f" active_target: {model_status.active_target}")
Before saving:
is_saved: False
active_target: None
next_target: /tmp/ogstools_root/Model/20260601_131430_445385
After saving:
is_saved: True
active_target: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/status_example
Object-Centric Design: Files as Technical Artifacts#
OGSTools follows an object-centric philosophy: you work with Model objects, and files are just technical artifacts created when needed.
# Load and work with objects in memory
model_obj = ot.Model(project=ot.Project(input_file=prj_path), meshes=meshes)
# Files are created only when you explicitly save
model_obj.save(work_dir / "model_artifact")
print(f"Files created at: {model_obj.active_target}")
Files created at: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/model_artifact
Cascading Storage: Hierarchical Save Propagation#
Saving a Model automatically saves all children (Project, Meshes, Execution).
model_cascade = ot.Model(
project=ot.Project(input_file=prj_path),
meshes=ot.Meshes.from_gmsh(ot.gmsh_tools.rect(n_edge_cells=5)),
)
print(f"Before save - Meshes is_saved: {model_cascade.meshes.is_saved}")
print(f"Before save - Project is_saved: {model_cascade.project.is_saved}")
model_cascade.save(work_dir / "cascade_model")
print(f"After save - Meshes is_saved: {model_cascade.meshes.is_saved}")
print(f"After save - Project is_saved: {model_cascade.project.is_saved}")
print("All components saved under the Model folder!")
# Show the folder structure
print("\nFolder structure created:")
for item in sorted((work_dir / "cascade_model").rglob("*")):
if item.is_file():
rel_path = item.relative_to(work_dir / "cascade_model")
print(f" {rel_path}")
Before save - Meshes is_saved: False
Before save - Project is_saved: False
After save - Meshes is_saved: True
After save - Project is_saved: True
All components saved under the Model folder!
Folder structure created:
.envrc
execution.yaml
meshes/bottom.vtu
meshes/domain.vtu
meshes/left.vtu
meshes/meta.yaml
meshes/right.vtu
meshes/top.vtu
project/default.prj
Control Child Storage: Pre-specifying Child Targets#
You can control where children are saved by specifying their targets BEFORE saving the parent. This is useful for sharing components between models.
# Create meshes and save to a shared location first
shared_meshes = ot.Meshes.from_gmsh(ot.gmsh_tools.rect(n_edge_cells=6))
shared_meshes.save(work_dir / "shared_meshes")
print(f"Meshes saved to: {shared_meshes.active_target}")
# Create a model with the pre-saved meshes
model_controlled = ot.Model(
project=ot.Project(input_file=prj_path), meshes=shared_meshes
)
print("\nBefore Model.save():")
print(f" Meshes user_specified_target: {shared_meshes.user_specified_target}")
# Save the model - meshes location is respected, not moved!
# It would be fine to decide here (short before model.save()) where to store the children (e.g. meshes)
model_controlled.save(work_dir / "controlled_model")
print("\nAfter Model.save():")
print(f" Model saved to: {model_controlled.active_target}")
print(f" Meshes still at: {model_controlled.meshes.active_target}")
print(" Meshes NOT moved into Model folder - pre-specified target respected!")
# Show both folder structures to illustrate the separation
print("\nModel folder structure:")
for item in sorted((work_dir / "controlled_model").rglob("*")):
if item.is_file():
rel_path = item.relative_to(work_dir / "controlled_model")
print(f" controlled_model/{rel_path}")
print("\nShared meshes folder (separate location):")
for item in sorted((work_dir / "shared_meshes").rglob("*")):
if item.is_file():
rel_path = item.relative_to(work_dir / "shared_meshes")
print(f" shared_meshes/{rel_path}")
Meshes saved to: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/shared_meshes
Before Model.save():
Meshes user_specified_target: True
After Model.save():
Model saved to: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/controlled_model
Meshes still at: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/shared_meshes
Meshes NOT moved into Model folder - pre-specified target respected!
Model folder structure:
controlled_model/.envrc
controlled_model/execution.yaml
controlled_model/project/default.prj
Shared meshes folder (separate location):
shared_meshes/bottom.vtu
shared_meshes/domain.vtu
shared_meshes/left.vtu
shared_meshes/meta.yaml
shared_meshes/right.vtu
shared_meshes/top.vtu
Deepcopy-Aware: Independent Storage for Copies#
Deepcopy creates an independent Model with a new save target.
from copy import deepcopy
original = ot.Model(project=project, meshes=meshes)
original.save(work_dir / "original_model")
copied = deepcopy(original)
print(f"Copy is_saved: {copied.is_saved}") # False - needs its own save
copied.save(work_dir / "copied_model")
print(f"Original: {original.active_target}")
print(f"Copy: {copied.active_target}")
# Show both folders exist independently
print("\nBoth models exist independently:")
print(f" original_model/ exists: {(work_dir / 'original_model').exists()}")
print(f" copied_model/ exists: {(work_dir / 'copied_model').exists()}")
Copy is_saved: False
Original: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/original_model
Copy: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/copied_model
Both models exist independently:
original_model/ exists: True
copied_model/ exists: True
UserPath and Organized Storage#
Setting UserPath creates organized, class-specific subdirectories for IDs.
ot.StorageBase.Userpath = work_dir / "organized"
# Do this always in the beginning and do not change it in the middle of your workflow.
# It is recommended to have a variable name similar to the id.
model_experiment_A = ot.Model(project=project, meshes=meshes, id="experiment_A")
model_experiment_A.save()
print(f"Saved to: {model_experiment_A.active_target}")
# Show the organized folder structure with class-specific subdirectories
print("\nUserPath folder structure:")
for item in sorted((work_dir / "organized").rglob("*")):
rel_path = item.relative_to(work_dir / "organized")
indent = " " * (len(rel_path.parts) - 1)
if item.is_dir():
print(f"{indent}{rel_path.name}/")
else:
print(f"{indent}{rel_path.name}")
Saved to: /tmp/ogstools_root/examples/storagef57931654b7a411db73088eeb1de5c7a/organized/Model/experiment_A
UserPath folder structure:
Model/
experiment_A/
.envrc
execution.yaml
meshes/
project/
## Roundtrip Methods
Saving and loading methods for each class:
ogstools.Model:
.save()→Model.from_folder(path)/Model.from_id(id)ogstools.Simulation:
.save()→Simulation.from_folder(path)/Simulation.from_id(id)ogstools.Meshes:
.save()→Meshes.from_folder(path)/Meshes.from_file(meta)ogstools.Project:
.save()→Project(input_file=path)ogstools.MeshSeries:
.save()→MeshSeries(filepath=path)ogstools.Execution:
.save()→Execution.from_file(path)
Total running time of the script: (0 minutes 1.139 seconds)