Quickstart¶
This guide walks you through creating and running your first metalab experiment.
Your First Experiment¶
Let's estimate the value of π using Monte Carlo simulation. Create a file called my_experiment.py:
import metalab
@metalab.operation
def estimate_pi(params, seeds, capture):
n = params["n_samples"]
rng = seeds.numpy()
x, y = rng.random(n), rng.random(n)
pi_est = 4.0 * (x**2 + y**2 <= 1).mean()
capture.metric("pi_estimate", pi_est)
exp = metalab.Experiment(
name="pi_mc",
version="0.1",
context={},
operation=estimate_pi,
params=metalab.grid(n_samples=[1000, 10000, 100000]),
seeds=metalab.seeds(base=42, replicates=3),
)
# Run and get results
handle = metalab.run(exp)
results = handle.result()
print(results.table())
Run it:
You'll see progress as your experiment runs, then a summary table of results.
Understanding the Components¶
Let's break down each part of the experiment.
The @operation Decorator¶
The @operation decorator marks a function as an experiment operation. Your operation function can declare any subset of the arguments that metalab provides (injected by name). In this quickstart we use:
params: A dictionary of parameter values for this runseeds: ASeedBundlefor controlled randomnesscapture: The capture interface for recording metrics and artifacts
If you need them, you can also request context and runtime. See Operations for details.
Operations should be pure functions of their inputs—all randomness must come from seeds, not global state.
The Experiment Definition¶
exp = metalab.Experiment(
name="pi_mc",
version="0.1",
context={},
operation=estimate_pi,
params=metalab.grid(n_samples=[1000, 10000, 100000]),
seeds=metalab.seeds(base=42, replicates=3),
)
An Experiment bundles together:
| Field | Purpose |
|---|---|
name |
Unique identifier for this experiment |
version |
Version string (helps track changes) |
context |
Input data specification (empty here, see Key Concepts) |
operation |
The function to run |
params |
Parameter source defining the sweep |
seeds |
Seed plan for reproducible randomness |
Parameter Grids¶
metalab.grid() creates a parameter grid. This example produces 3 parameter combinations:
{"n_samples": 1000}{"n_samples": 10000}{"n_samples": 100000}
For multiple parameters, it generates the full Cartesian product:
metalab.grid(
n_samples=[1000, 10000],
method=["basic", "stratified"]
)
# Produces 4 combinations (2 × 2)
Seed Plans¶
metalab.seeds() creates a seed plan with:
base: The base seed for reproducibilityreplicates: Number of independent runs per parameter combination
With 3 parameter values and 3 replicates, this experiment runs 9 times total (3 × 3).
Inside your operation, get random number generators from the seed bundle:
rng = seeds.numpy() # NumPy Generator
rng = seeds.rng() # Python random.Random instance
seed_int = seeds.seed() # Raw integer seed
Capturing Results¶
The capture interface records outputs from your operation:
capture.metric(name, value): Numeric metrics (stored in run record)capture.data(name, value): Structured data (JSON-serializable or numpy arrays)capture.artifact(name, data, format): Binary artifacts (images, models, etc.)capture.log(message): Log messages for debugging
Metrics are lightweight and appear in result tables. Use capture.data() or capture.artifact() for larger outputs.
Running the Experiment¶
metalab.run() returns a RunHandle immediately. Call .result() to block until completion and get a Results object.
You can also check progress without blocking:
handle = metalab.run(exp)
print(handle.status) # Check current status
# ... do other work ...
results = handle.result() # Block when ready
Viewing Results¶
Quick Table View¶
Displays a formatted table of all runs with their parameters and metrics.
DataFrame Export¶
For analysis in pandas:
df = results.to_dataframe()
print(df)
# Filter and analyze
successful = df[df["status"] == "completed"]
print(successful.groupby("n_samples")["pi_estimate"].mean())
This requires the pandas extra:
Accessing Individual Runs¶
for run in results:
print(f"Run {run.run_id}: π ≈ {run.metrics['pi_estimate']:.4f}")
print(f" Params: {run.params}")
print(f" Status: {run.status}")
Specifying a Store Location¶
By default, results are stored in a temporary directory. To persist results:
This creates a FileStore at the specified path. You can reload results later:
Key Features You Get Automatically¶
Without any extra configuration, metalab provides:
- Deterministic run IDs: Each run has a stable ID derived from experiment + params + seeds
- Automatic deduplication: Re-running skips already-completed runs
- Resume support: Interrupted experiments continue where they left off
- Parallel execution: Runs execute concurrently by default
Project Configuration¶
As your experiments grow, you'll want shared infrastructure settings (executor defaults, store locations) that don't clutter each experiment file.
metalab uses a layered configuration system driven by .metalab.toml:
# .metalab.toml (project root, committed to git)
[project]
name = "my-project"
default_env = "local"
[environments.local]
type = "local"
file_root = "./runs"
[environments.slurm]
type = "slurm"
gateway = "hpc.university.edu"
file_root = "/shared/experiments"
# Resources for infrastructure services (postgres + atlas)
[environments.slurm.services]
partition = "cpu2019"
time = "7-00:00:00"
memory = "10G"
# Resources for experiment jobs
[environments.slurm.executor]
partition = "gpu"
time = "1:00:00"
memory = "32G"
Key features:
- Auto-discovery: metalab walks up from your working directory to find
.metalab.toml— no explicit paths needed. - Local overrides:
.metalab.local.toml(gitignored) is deep-merged on top for machine-specific or sensitive values like credentials. - Separated resource specs: Service infrastructure and experiment job resources are configured independently under
[environments.*.services]and[environments.*.executor]. - Store discovery: With services running (
metalab services up), usestore="discover"to auto-connect to the provisioned database.
# Your experiment only specifies overrides — defaults come from .metalab.toml
handle = metalab.run(
exp,
store="discover",
executor=metalab.resolve_executor("slurm", {"gpus": 1}),
)
See Services and Environments for the full configuration guide, including environment profiles, SSH tunneling, and service provisioning.
Next Steps¶
Now that you've run your first experiment, explore more:
- Key Concepts — Context specs, parameter sources, and seed discipline
- Execution — Local executors and SLURM cluster runs
- Storage — FileStore, PostgreSQL, and data transfer