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.

from pathlib import Path
from tempfile import mkdtemp

import ogstools as ot
from ogstools.definitions import EXAMPLES_DIR
from ogstools.gmsh_tools import rect

# Set up a temporary directory for our examples
work_dir = Path(mkdtemp())
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(rect(n_edge_cells=10))
prj_path = 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/tmpr_ma5f_f/my_model

Loading a Model#

Load a previously saved model using its path.

loaded_model = ot.Model.from_folder(work_dir / "my_model")
print(loaded_model)
Model id: 20260323_113702_195936
   Model(saved to: file:///tmp/tmpr_ma5f_f/my_model)
  Project: saved to file:///tmp/tmpr_ma5f_f/my_model/project
  Meshes: saved to file:///tmp/tmpr_ma5f_f/my_model/meshes
  Execution: saved to file:///tmp/tmpr_ma5f_f/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/tmpr_ma5f_f/Model/experiment_1
Loaded by ID: /tmp/tmpr_ma5f_f/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/tmpr_ma5f_f/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/tmpr_ma5f_f/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: 20260323_113702_097341
   Model(saved to: file:///tmp/tmpr_ma5f_f/archived)
  Project: saved to file:///tmp/tmpr_ma5f_f/my_model/project
  Meshes: saved to file:///tmp/tmpr_ma5f_f/my_model/meshes
  Execution: saved to file:///tmp/tmpr_ma5f_f/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.Userpath for 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/20260323_113702_305268

After saving:
  is_saved: True
  active_target: /tmp/tmpr_ma5f_f/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/tmpr_ma5f_f/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(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(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/tmpr_ma5f_f/shared_meshes

Before Model.save():
  Meshes user_specified_target: True

After Model.save():
  Model saved to: /tmp/tmpr_ma5f_f/controlled_model
  Meshes still at: /tmp/tmpr_ma5f_f/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/tmpr_ma5f_f/original_model
Copy: /tmp/tmpr_ma5f_f/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/tmpr_ma5f_f/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 0.984 seconds)