State, URI, and source-management demos
Overview
These scripts highlight the ways dynlib exposes the compiled artifacts, externalizes simulation state, and resolves models from different URI schemes. Use them when you want to checkpoint a run, save or reload presets, export generated Python source for inspection, or understand how setup() picks a model from inline text, files, or config-based tags.
Example scripts
Snapshots & export/import
# examples/snapshot_demo.py
"""
Demonstration of snapshot export/import functionality in dynlib.
This example shows how to:
1. Export current simulation state to disk (including workspaces)
2. Export named snapshots to disk
3. Import snapshots from disk
4. Inspect snapshot metadata
5. Demonstrate workspace persistence across sessions
"""
from pathlib import Path
import tempfile
import tomllib
from dynlib import setup
def create_model():
"""Create a simple exponential decay model for demonstration."""
model_uri = """
inline:
[model]
type = "ode"
name = "Demo Exponential Decay"
dtype = "float64"
[states]
x = 1.0
[params]
decay_rate = 0.1
[equations.rhs]
x = "-decay_rate * x"
[sim]
stepper = "euler"
dt = 0.01
t0 = 0.0
t_end = 10.0
record = true
"""
return setup(model_uri, jit=True)
def main():
print("=== Dynlib Snapshot Export/Import Demo ===\n")
# Create simulation
sim = create_model()
print("✓ Created simulation with exponential decay model")
# Run to some intermediate state
sim.run(T=2.0)
print(f"✓ Ran simulation to t=2.0, current state: x={sim.state_vector()[0]:.4f}")
# Create an in-memory snapshot
sim.create_snapshot("checkpoint_1", "After running to t=2.0")
print("✓ Created in-memory snapshot 'checkpoint_1'")
# Continue running
sim.run(T=5.0, resume=True)
print(f"✓ Continued to t=5.0, current state: x={sim.state_vector()[0]:.4f}")
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_path = Path(tmp_dir)
# Export current state
current_snap_path = tmp_path / "current_state.npz"
sim.export_snapshot(current_snap_path, source="current")
print(f"✓ Exported current state to {current_snap_path.name}")
# Export the named snapshot
named_snap_path = tmp_path / "checkpoint_1.npz"
sim.export_snapshot(named_snap_path, source="snapshot", name="checkpoint_1")
print(f"✓ Exported named snapshot to {named_snap_path.name}")
# Inspect the snapshots without loading them
print("\n--- Snapshot Inspection ---")
for path in [current_snap_path, named_snap_path]:
meta = sim.inspect_snapshot(path)
print(f"{path.name}:")
print(f" Schema: {meta['schema']}")
print(f" Name: {meta['name']}")
print(f" Time: {meta['t_curr']:.3f}")
print(f" State: x={meta['state_names'][0]}")
print(f" Workspace signature: {meta['pins']['workspace_sig']}")
print(f" Created: {meta['created_at']}")
print()
# Continue simulation further
sim.run(T=8.0, resume=True)
current_x = sim.state_vector()[0]
print(f"✓ Continued to t=8.0, current state: x={current_x:.4f}")
# Import the earlier snapshot
print("\n--- Importing Snapshot ---")
sim.import_snapshot(named_snap_path)
restored_x = sim.state_vector()[0]
restored_t = sim.session_state_summary()["t"]
print(f"✓ Imported snapshot from t=2.0")
print(f" Restored state: t={restored_t:.3f}, x={restored_x:.4f}")
# Verify results are cleared
try:
results = sim.raw_results()
print("✗ ERROR: Results should be cleared after import!")
except RuntimeError as e:
print(f"✓ Results correctly cleared: {e}")
# Can continue from restored state
sim.run(T=3.0, resume=True)
final_x = sim.state_vector()[0]
print(f"✓ Resumed from restored state to t=3.0, x={final_x:.4f}")
# Demonstrate workspace persistence
print("\n--- Workspace Persistence ---")
# Create a new simulation to show workspace restoration
sim2 = create_model()
sim2.run(T=1.0) # Run to different state
# Export current state with workspace
ws_snap_path = tmp_path / "workspace_demo.npz"
sim.export_snapshot(ws_snap_path, source="current")
print("✓ Exported simulation state with workspace")
# Import into new simulation
sim2.import_snapshot(ws_snap_path)
print("✓ Imported state with workspace into new simulation")
# Verify state was restored
restored_state = sim2.session_state_summary()
print(f" Restored time: {restored_state['t']:.3f}")
print(f" Restored state: x={sim2.state_vector()[0]:.4f}")
print(f" Can resume: {restored_state['can_resume']}")
print("\n=== Demo Complete ===")
if __name__ == "__main__":
main()
sim.create_snapshot, sim.export_snapshot, and sim.import_snapshot. The script prints snapshot metadata, shows how workspaces survive across exports, and verifies that sim.results() is cleared after restoring a snapshot so you can safely resume from arbitrary points.
Exporting the compiled sources
# examples/export_sources_demo.py
"""
Demonstration of compiled model source code export functionality.
This example shows how to:
1. Build a compiled model using setup()
2. Export the generated Python source code for inspection
3. Verify the compilation is correct
"""
from pathlib import Path
import tempfile
from dynlib import setup
def main():
# Use an existing test model
model_path = Path(__file__).parent.parent / "tests" / "data" / "models" / "decay.toml"
# Setup simulation with JIT compilation
print(f"Building model from: {model_path.name}")
sim = setup(str(model_path), stepper="euler", jit=True, disk_cache=False)
# Check if source code is available
print(f"\nSource code availability:")
print(f" RHS source: {'✓' if sim.model.rhs_source else '✗'}")
print(f" Events pre source: {'✓' if sim.model.events_pre_source else '✗'}")
print(f" Events post source: {'✓' if sim.model.events_post_source else '✗'}")
print(f" Stepper source: {'✓' if sim.model.stepper_source else '✗'}")
# Export sources to a temporary directory
with tempfile.TemporaryDirectory() as tmpdir:
export_dir = Path(tmpdir) / "compiled_model"
print(f"\nExporting sources to: {export_dir}")
exported_files = sim.model.export_sources(export_dir)
print(f"\nExported files:")
for component, filepath in exported_files.items():
size = filepath.stat().st_size if filepath.exists() else 0
print(f" {component}: {filepath.name} ({size} bytes)")
# Display the RHS source code
if "rhs" in exported_files:
print(f"\n{'='*60}")
print("RHS Function Source Code:")
print('='*60)
rhs_content = exported_files["rhs"].read_text()
print(rhs_content)
print('='*60)
# Display the stepper source code if available
if "stepper" in exported_files:
print(f"\n{'='*60}")
print("Stepper Function Source Code:")
print('='*60)
stepper_content = exported_files["stepper"].read_text()
print(stepper_content)
print('='*60)
print("\n✓ Export demo completed successfully!")
if __name__ == "__main__":
main()
tests/data/models/decay.toml, toggles disk_cache=False, and calls sim.model.export_sources() into a temporary directory. The demo checks that the RHS, events, and stepper sources exist, prints their sizes, and shows the contents so you can review what dynlib emitted for debugging or compliance.
Preset workflows
#!/usr/bin/env python3
"""
Demo of the presets feature in dynlib v2.
Shows:
- Inline presets defined in the model DSL
- Listing and applying presets
- Loading presets from external files
- Saving presets to files
- Capturing runtime presets with add_preset
- Round-trip preservation
"""
import tempfile
from pathlib import Path
import tomllib
from dynlib.dsl.parser import parse_model_v2
from dynlib.dsl.spec import build_spec
from dynlib.compiler.build import build
from dynlib.runtime.sim import Sim
# Model with inline presets
MODEL_TOML = """
[model]
type = "ode"
name = "Izhikevich Neuron (Simple)"
[states]
v = -65.0
u = -13.0
[params]
a = 0.02
b = 0.2
c = -65.0
d = 8.0
I = 10.0
[equations.rhs]
v = "0.04*v*v + 5*v + 140 - u + I"
u = "a*(b*v - u)"
[events.spike]
phase = "post"
cond = "v >= 30"
log = ["t"]
[events.spike.action]
v = "c"
u = "u + d"
[sim]
t0 = 0.0
t_end = 200.0
dt = 0.25
stepper = "euler"
record = true
# Inline presets for different neuron behaviors
[presets.regular_spiking.params]
a = 0.02
b = 0.2
c = -65.0
d = 8.0
I = 10.0
[presets.fast_spiking.params]
a = 0.1
b = 0.2
c = -65.0
d = 2.0
I = 10.0
[presets.bursting.params]
a = 0.02
b = 0.2
c = -50.0
d = 2.0
I = 15.0
[presets.bursting.states]
v = -70.0
u = -14.0
"""
def main():
# Load model
print("=== Loading model ===")
doc = tomllib.loads(MODEL_TOML)
spec = build_spec(parse_model_v2(doc))
model = build(spec, stepper=spec.sim.stepper, jit=True)
sim = Sim(model)
# List inline presets
print("\n=== Inline presets ===")
presets = sim.list_presets()
print(f"Available presets: {presets}")
# Apply a preset and run
print("\n=== Applying 'regular_spiking' preset ===")
sim.apply_preset("regular_spiking")
sim.run(T=100.0)
results = sim.results()
event_counts = results.event.summary()
n_spikes = event_counts.get('spike', 0)
print(f"Regular spiking: {n_spikes} spikes in 100ms")
# Apply different preset
print("\n=== Applying 'fast_spiking' preset ===")
sim.reset() # Reset to initial state
sim.apply_preset("fast_spiking")
sim.run(T=100.0)
results = sim.results()
event_counts = results.event.summary()
n_spikes = event_counts.get('spike', 0)
print(f"Fast spiking: {n_spikes} spikes in 100ms")
# Save presets to file
with tempfile.TemporaryDirectory() as tmpdir:
preset_file = Path(tmpdir) / "neuron_presets.toml"
print(f"\n=== Saving presets to {preset_file.name} ===")
sim.reset()
sim.apply_preset("regular_spiking")
sim.add_preset("regular_spiking", overwrite=True)
sim.save_preset("regular_spiking", preset_file)
sim.apply_preset("fast_spiking")
sim.add_preset("fast_spiking", overwrite=True)
sim.save_preset("fast_spiking", preset_file)
# Check file contents
with open(preset_file, "rb") as f:
doc = tomllib.load(f)
print(f"File contains: {list(doc['presets'].keys())}")
# Create a new sim and load from file
print("\n=== Loading presets from file ===")
sim2 = Sim(model)
count = sim2.load_preset("*", preset_file, on_conflict="replace") # warning should be raised
print(f"Loaded {count} presets")
print(f"Available: {sim2.list_presets()}")
# Verify they work
sim2.apply_preset("regular_spiking")
sim2.run(T=100.0)
results2 = sim2.results()
event_counts2 = results2.event.summary()
n_spikes2 = event_counts2.get('spike', 0)
print(f"Loaded preset produces {n_spikes2} spikes (should match original)")
# Demonstrate glob patterns
print("\n=== Glob pattern matching ===")
print(f"All presets: {sim.list_presets('*')}")
print(f"Presets starting with 'fast': {sim.list_presets('fast_*')}")
print(f"Presets ending with 'spiking': {sim.list_presets('*_spiking')}")
print("\n=== Demo complete ===")
if __name__ == "__main__":
main()
regular_spiking, fast_spiking, and bursting presets. It saves the presets to a temporary file, loads them back into a second Sim, and proves that the loaded presets reproduce the spike counts while showcasing glob-style preset matching.
URI and path resolution
#!/usr/bin/env python3
"""
This script demonstrates the new path resolution and URI system,
showing various ways to load and run models.
"""
from dynlib import setup
def demo_inline_model():
"""Demo 1: Load a model from inline TOML."""
print("=" * 60)
print("Demo 1: Inline Model Definition")
print("=" * 60)
inline_model = """
inline:
[model]
type = "ode"
[states]
x = 1.0
[params]
a = 1.0
[equations.rhs]
x = "-a * x"
[sim]
t0 = 0.0
t_end = 2.0
dt = 0.1
stepper = "euler"
"""
uri = inline_model
sim = setup(uri, stepper="euler", jit=False)
print(f"Model kind: {sim.model.spec.kind}")
print(f"States: {sim.model.spec.states}")
print(f"Stepper: {sim.model.stepper_name}")
# Run simulation
sim.run(T=2.0)
results = sim.results()
print(f"Simulation ran {len(results)} steps")
print(f"Initial x: {results['x'][0]:.6f}")
print(f"Final x: {results['x'][-1]:.6f}")
print()
def demo_file_loading():
"""Demo 2: Load a model from existing test file."""
print("=" * 60)
print("Demo 2: File-based Model Loading")
print("=" * 60)
from pathlib import Path
data_dir = Path(__file__).parent.parent / "tests" / "data" / "models"
model_path = data_dir / "decay.toml"
if not model_path.exists():
print(f"Test data not found: {model_path}")
return
# Load using absolute path
sim = setup(str(model_path), stepper="euler", jit=False)
print(f"Loaded from: {model_path}")
print(f"Model kind: {sim.model.spec.kind}")
print(f"States: {sim.model.spec.states}")
print(f"Default stepper: {sim.model.stepper_name}")
print()
def demo_uri_schemes():
"""Demo 3: Show different URI schemes."""
print("=" * 60)
print("Demo 3: URI Scheme Examples")
print("=" * 60)
examples = [
("inline: [model]\\ntype='ode'", "Inline TOML definition"),
("/abs/path/model.toml", "Absolute file path"),
("relative/model.toml", "Relative path from cwd"),
("model", "Extensionless (tries model.toml)"),
("proj://model.toml", "TAG resolution from config"),
("model.toml#mod=fast", "Fragment selector for mods"),
("TAG://path/to/model.toml#mod=variant", "Combined TAG + fragment"),
]
print("Supported URI schemes:")
for uri, description in examples:
print(f" {uri:40s} - {description}")
print()
print("Configuration:")
print(" Config file: ~/.config/dynlib/config.toml (Linux)")
print(" Or: ~/Library/Application Support/dynlib/config.toml (macOS)")
print(" Or: %APPDATA%\\dynlib\\config.toml (Windows)")
print()
print(" Environment overrides:")
print(" DYNLIB_CONFIG=/custom/config.toml")
print(" DYN_MODEL_PATH=proj=/extra/path,/another")
print()
def main():
"""Run all demos."""
print("\n")
print("╔" + "═" * 58 + "╗")
print("║" + " " * 10 + "DYNLIB URI System Demo" + " " * 26 + "║")
print("╚" + "═" * 58 + "╝")
print()
demo_inline_model()
demo_file_loading()
demo_uri_schemes()
print("=" * 60)
print("Demo Complete!")
print("=" * 60)
if __name__ == "__main__":
main()
inline: scheme,
- loading from absolute and relative filesystem paths,
- showing how to use extensionless names, fragments (#mod=), and proj:///TAG:// references,
- and reminding you where config files live (~/.config/dynlib/config.toml, %APPDATA% on Windows, etc.).
The script prints what each invocation loaded to make debug easier when a model path is ambiguous.