Extending a Monte Carlo¶
A 100-run Monte Carlo isn't always enough — the running mean might still drift, the standard error might still be wide. monte_carlo_extend appends n more runs at run_ids [old_n, old_n + n) whose per-parameter draws are bit-equal to the same indices of a fresh monte_carlo(n=old_n + n, seed=...) call. The original 100 run_ids are preserved bit-for-bit.
This notebook walks through that contract end-to-end: 100-run base, extend by 200, then assert that the first 100 rows of the 300-run frame are byte-identical to the original 100-run frame.
Prerequisites. A local GMAT install (R2026a is the primary development target; see Supported versions). This notebook does not depend on the [examples] extra (no plots).
Set up the run¶
The same four-axis injection-dispersion fixture notebook 04 uses; monte_carlo_extend requires the manifest from a prior monte_carlo call, so we'll anchor the base run with out= and feed the manifest path back in.
import tempfile
from pathlib import Path
import pandas as pd
from gmat_run import locate_gmat
from gmat_sweep import monte_carlo, monte_carlo_extend
install = locate_gmat()
script_path = Path("injection_dispersion.script").resolve()
print(f"GMAT version: {install.version}")
print(f"Script: {script_path.name}")
print(f"Exists: {script_path.exists()}")
GMAT version: R2026a Script: injection_dispersion.script Exists: True
Run the base 100-run Monte Carlo¶
Same perturb shorthand as notebook 04, slightly tighter on the dispersions to keep wall-clock down. The seed is recorded on the manifest the sweep writes; monte_carlo_extend reads it back so the caller cannot accidentally change it on extension (which would break determinism).
SEED = 20260508
perturb = {
"CoastTime.Value": ("normal", 600.0, 30.0),
"Inj.Element1": ("normal", 1.0, 0.005),
"Inj.Element2": ("normal", 0.0, 0.005),
"Inj.Element3": ("normal", 0.0, 0.005),
}
tmpdir = tempfile.TemporaryDirectory(prefix="mc-extend-")
base_out = Path(tmpdir.name) / "base"
df_base = monte_carlo(
script_path,
n=100,
perturb=perturb,
seed=SEED,
out=base_out,
progress=False,
)
print(f"Base run_id range: 0..{df_base.index.get_level_values('run_id').max()}")
df_base["__status"].value_counts()
Base run_id range: 0..99
__status ok 1200 Name: count, dtype: int64
Extend by 200 more runs¶
monte_carlo_extend reloads the manifest, reads the original (perturb, seed), dispatches 200 new runs at run_ids 100..299, and returns the aggregated DataFrame over all 300 runs. Per-parameter draws at the new run_ids are bit-equal to the same indices of a fresh monte_carlo(n=300, seed=SEED) call thanks to the position-determinism of numpy.random.SeedSequence.spawn.
df_full = monte_carlo_extend(
base_out / "manifest.jsonl",
script_path,
n=200,
progress=False,
)
print(f"Extended run_id range: 0..{df_full.index.get_level_values('run_id').max()}")
print(f"Total run_id count: {df_full.index.get_level_values('run_id').nunique()}")
Extended run_id range: 0..299 Total run_id count: 300
Determinism check on the original 100¶
Slice the 300-run frame to run_id < 100 and compare against the 100-run base frame. The contract is row-equivalence — same (run_id, time) index, same data columns, same values.
This is the same assertion the package's own determinism test (tests/test_monte_carlo_determinism.py) makes — pinned at the user-visible DataFrame level so any silent draw drift would surface here too.
preserved = df_full.loc[df_full.index.get_level_values("run_id") < 100]
pd.testing.assert_frame_equal(preserved, df_base)
print("Original 100 run_ids are preserved bit-for-bit after the extension.")
Original 100 run_ids are preserved bit-for-bit after the extension.
Where to next¶
- Why Latin hypercube can't be extended.
latin_hypercube_extendrefuses cleanly: extending an LH sweep changes the per-axis stratification of every existing sample. The function exists to raise a clearSweepConfigErrorrather than anAttributeErrorif a generic "extend whatever sweep this is" wrapper hits an LH manifest. - Per-run determinism inspection.
expand_monte_carlo_to_run_specsandexpand_monte_carlo_extension_to_run_specsbuild the per-run override dicts the workers would receive — useful for diffing a draw set without paying the cost of running it. - Programmatic resume from a kill. Notebook 03 walks through interrupting a sweep with
SIGINTand completing it withSweep.from_manifest(...).resume()— the same on-disk manifest contractmonte_carlo_extendreads from.