Bundle Loading Guide
TL;DR
Open a .sdsge bundle with load_bundle(...) and reach every component through the typed LoadedBundle fields — the re-solved SolvedModels, the estimation spec / result / observed data / posterior, the Monte Carlo pipeline / result / traces, and the simulation prefill. Loading is deterministic: the policy matrices match the author's.
You can find a demonstration notebook here.
This guide walks through opening a .sdsge bundle and reaching each library object it carries — the re-solved SolvedModels, the estimation spec / result / observed data / posterior, the Monte Carlo pipeline / result / traces, and the simulation prefill.
We use experiment-1.sdsge as produced by the Bundle Authoring Guide. Substitute any other bundle path.
What load_bundle actually does
load_bundle re-parses every embedded YAML, re-runs DSGESolver.compile(**compile_kwargs).solve(**solve_kwargs) with the kwargs recorded at write time, and decodes every tabular member by Member.format (CSV or Parquet). Loading is deterministic — the resulting policy matrices match those the author had in hand.
Open the bundle
load_bundleis a top-level re-export ofSymbolicDSGE.bundle.build_from. Both names are interchangeable.
loaded is a LoadedBundle — every component is reachable through a typed field.
print("Created by:", loaded.manifest.created_by)
print("Created at:", loaded.manifest.created_at)
print("Format version:", loaded.manifest.sdsge_version)
Manifest provenance
Manifest.checksums carries SHA-256 hex digests over each member's bytes — useful for integrity checks before trusting the contents downstream.
Reach the re-solved models
reference and dgp are full SolvedModel instances. They behave exactly like models you would have solved in-process — including IRFs, simulation, and Kalman filtering.
reference = loaded.reference
if reference is not None:
print("Stable:", reference.policy.stab == 0)
print("Eigenvalues:", reference.policy.eig)
print("A shape:", reference.A.shape)
A quick deterministic simulation against pre-generated shocks confirms the policy round-tripped:
import numpy as np
T = 20
rng = np.random.default_rng(42)
shocks = {
"g,z": rng.standard_normal((T, 2)), # (1)!
}
sim = reference.sim(
T=T,
shocks=shocks,
observables=True,
)
print(sim["Infl"][:5])
- See
SolvedModel.simfor the shock specification grammar.
DGP slot may be absent
loaded.dgp is None whenever the bundle did not carry a dgp.yaml. Test before use.
Reach the estimation tab
LoadedEstimation carries every text and bulk component of the estimation run.
estimation = loaded.estimation
if estimation is not None:
print("Method:", estimation.spec.method) # (1)!
print("Observables:", estimation.spec.observables)
print("Parameters:", [p.name for p in estimation.spec.parameters])
estimation.specis anEstimationSpecinstance — round-trippable to / from JSON viato_dict()/from_dict().
The result metadata discriminates by type. For MLE / MAP runs the result is an OptimizationResultMeta; for MCMC it is an MCMCResultMeta paired with bulk traces in estimation.posterior.
from SymbolicDSGE.estimation.spec import (
MCMCResultMeta,
OptimizationResultMeta,
)
result = estimation.result
if isinstance(result, OptimizationResultMeta):
print("Point estimate:", result.theta)
print("Log-posterior:", result.logpost)
elif isinstance(result, MCMCResultMeta):
print("Acceptance:", result.accept_rate)
print("Draws:", result.n_draws, "burn-in:", result.burn_in)
Observed data and (when present) MCMC posterior are numpy arrays decoded from the embedded CSV or Parquet member.
if estimation.observed is not None:
print("Observed shape:", estimation.observed.shape) # (1)!
if estimation.posterior is not None:
samples = estimation.posterior["samples"] # (2)!
print("Posterior mean:", samples.mean(axis=0))
- Shape is
(n_periods, n_observables)with column order matchingestimation.spec.observables. - Pair with
result.param_namesfor column-to-parameter mapping. Thelogpostkey holds the 1-D log-posterior trace.
MCMCResult reconstruction
MCMCResultMeta carries the scalar slice only. To rebuild a live MCMCResult for sampling diagnostics, construct it explicitly:
from SymbolicDSGE.estimation.results import MCMCResult
mcmc = MCMCResult(
param_names=result.param_names,
samples=estimation.posterior["samples"],
logpost_trace=estimation.posterior["logpost"],
accept_rate=result.accept_rate,
n_draws=result.n_draws,
burn_in=result.burn_in,
thin=result.thin,
)
mcmc.hpd_intervals(alpha=0.05)
Re-run an estimation from a loaded bundle
EstimationSpec.to_estimator_inputs() lowers the loaded spec to concrete arguments — estimated_params, theta0, bounds, and priors (built Prior objects, not specs) — directly feedable to DSGESolver.estimate(...). The lowering lives in the core library, so no [ui] extra is required.
from SymbolicDSGE import DSGESolver
inputs = estimation.spec.to_estimator_inputs() # (1)!
solver = DSGESolver(loaded.reference.config, loaded.reference.kalman_config)
extras: dict = {} # (2)!
if inputs.bounds is not None and estimation.spec.method != "mcmc":
extras["bounds"] = inputs.bounds
fresh_result = solver.estimate(
compiled=loaded.reference.compiled, # (3)!
y=estimation.observed, # (4)!
method=estimation.spec.method,
estimated_params=inputs.estimated_params,
theta0=inputs.theta0,
priors=inputs.priors,
observables=estimation.spec.observables,
**extras,
)
- Selects
estimate=Trueparameters, materializes their initials/bounds, and (for MAP/MCMC) builds aPriorobject from eachPriorSpec. Raises if MAP/MCMC parameters lack a prior. solver.estimateforwards**method_kwargsto the underlyingmle/map/mcmccall.boundsis accepted by MLE/MAP but not by MCMC, so we gate it on the method.- The
CompiledModelreuses the layoutload_bundlealready produced when re-solving the embedded YAML — no need to recompile. - The observed matrix the original run was fit against — already reconstructed by
load_bundleand stored onLoadedEstimation.observed.
Why to_estimator_inputs exists
The spec is human-authored (or GUI-authored): it carries PriorSpec for declarative reasons. The estimator needs built Prior objects. to_estimator_inputs is the seam where the materialization happens, and where MAP/MCMC's prior-required invariant is enforced.
See the Estimation Guide for the run methods in detail.
Reach the Monte Carlo tab
LoadedMC.spec is the PipelineSpec describing the graph. LoadedMC.resources carries any side-channel arrays or custom callables referenced by raw-data/custom nodes. When the bundle carries a completed run, document holds the trace-free summary and traces holds the bulk columns. The convenience method wire() re-merges them into the canonical UI shape.
mc = loaded.mc
if mc is not None:
print("Pipeline nodes:", [n.id for n in mc.spec.nodes])
print("Pipeline edges:", [(e.source, e.target) for e in mc.spec.edges])
if mc.document is not None:
wire = mc.wire() # (1)!
print("Run kind:", wire["kind"])
mc.wire()returnsNonewhen eitherdocumentortracesis missing — a bundle authored with only the pipeline spec carries neither.
Re-run a Monte Carlo pipeline from a loaded bundle
The pipeline runner lives in the core monte_carlo module — a loaded pipeline runs against the loaded models without the [ui] extra.
from SymbolicDSGE.monte_carlo import run_pipeline
mc_result = run_pipeline(
loaded.mc.spec, # (1)!
reference=loaded.reference,
dgp=loaded.dgp,
n_rep=500,
fail_fast=True,
resources=loaded.mc.resources, # (2)!
)
print("Successful reps:", mc_result.n_successful, "/", mc_result.n_rep)
run_pipelinevalidates the graph againstSTEP_CATALOG, compiles each step, and runs it.validate_pipeline_specandbuild_pipelineare also exported when you want the stages separately.resourcesis required when the stored spec references raw-data arrays or custom operations. It is harmless for specs that do not need side-channel resources.
Validating without running
validate_pipeline_spec(loaded.mc.spec, has_reference=loaded.reference is not None, has_dgp=loaded.dgp is not None) returns the topologically ordered node list when the graph is well-formed and raises with a specific message otherwise — useful to surface validation errors before a long run.
See the Monte Carlo Guide for the pipeline grammar and the monte_carlo API reference for the core runner exports.
Reach the simulation prefill
The SimSpec rides inline in the manifest, so it is reachable from loaded.simulation directly (no separate member).
sim_spec = loaded.simulation
if sim_spec is not None:
print("T:", sim_spec.T)
print("Observables flag:", sim_spec.observables)
if sim_spec.shock_generation is not None:
print("Seed:", sim_spec.shock_generation.seed)
print("Distribution:", sim_spec.shock_generation.dist)
Determinism
Replaying sim_spec against loaded.reference reproduces the author's intended simulation exactly. The bundle stores no simulation outputs — they are reconstructed on demand from (SolvedModel, seed, shock spec).
Round-trip safety
Two properties to rely on after load_bundle:
- Manifest integrity: every member declared in the manifest is present in the archive (and vice versa).
BundleArchive.openvalidates this on read and raises if the bundle is malformed. - Format-version compatibility: a newer-than-supported bundle raises immediately with a clear message. Older bundles read forward without intervention.
Reproducing simulations across machines
Deterministic reproduction requires the receiver run the same numpy / SciPy versions on the same platform. The bundle does not pin those — record them externally if exact reproducibility across heterogeneous environments matters.
Further steps
sdsge-decompile— extract the same components to disk for inspection or editing.LoadedBundleAPI reference.- Bundle Authoring Guide — the other half of the round-trip.