Skip to content

Commit 1c08ea5

Browse files
vosesoftclaude
andcommitted
Persist sim options before StartSimul so the seed is honored (AB#2742)
run_simulation(seed=N) packed the seed correctly ([SeedFixed]:1 / [seed0]:N, matching ModelRisk's PackToStringList), and the array marshalled fine — verified by reading back ModelRisk's own parsed SimOpt_SeedFixed=1 / SimOpt_Seed0=N after feeding the exact payload to VoseSetSimulOptions12. But the simulation still ran non-reproducibly. Root cause: ModelRisk's per-cell-twister Manual-Seed mode sources its base seed from the WORKBOOK-persisted SimOpt_SeedFixed / SimOpt_Seed0 defined-names (written by SaveOptionsToWorkbook), NOT from the per-call options passed to VoseStartSimulCustom12. The bridge set the per-call [seed0] (ignored by the twister) but never persisted it to the workbook, so Manual Seed stayed OFF -> Random mode -> different stream every run. Fix: call VoseSetSimulOptions12 with the same options payload before VoseStartSimulCustom12. That runs SaveOptionsToWorkbook, persisting SimOpt_SeedFixed=1 / SimOpt_Seed0=N, which the twister then honors. Verified end-to-end against the engine oracle (single-cell VoseUniform): - two runs at seed=12345 -> byte-identical streams (was: differing) - seed=12345 vs seed=555 -> different reproducible streams (seed now drives it) - the seed=12345 stream matches mrengine's per-cell twister-0 reference (0.929616, 0.890155, 0.316376, 0.130707, 0.183919, 0.039759, ...) exactly, validating the byte-identical parity path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9b01c48 commit 1c08ea5

1 file changed

Lines changed: 12 additions & 0 deletions

File tree

src/modelrisk_mcp/bridge/simulation.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ class SimulationRunResult:
124124
# "Vose" + X (ModelRiskCloude/CLAUDE.md:63).
125125
_CMD_START_SIM = "VoseStartSimulCustom12"
126126
_CMD_GET_DATA_SZ = "VoseGetDataSZ12"
127+
# Persists the simulation options to the workbook as SimOpt_* defined-names via
128+
# SaveOptionsToWorkbook (SimulationCommonOptionsReadWrite.cpp). REQUIRED for the
129+
# seed: ModelRisk's per-cell-twister Manual-Seed mode reads its base seed from the
130+
# workbook-persisted SimOpt_SeedFixed / SimOpt_Seed0 names, NOT from the per-call
131+
# options handed to VoseStartSimulCustom12. Without this call the per-call
132+
# [SeedFixed]/[seed0] are parsed but ignored by the twister -> Random mode ->
133+
# non-reproducible streams (AB#2742; verified end-to-end against the engine oracle).
134+
_CMD_SET_SIM_OPTS = "VoseSetSimulOptions12"
127135

128136
# Operation prefix the SimulationObj_VBA dispatcher matches on for the
129137
# save path. PackSessionName format: "h<hwnd>_<Operation>_<book_name>"
@@ -346,6 +354,10 @@ def _invoke_start_simulation(self, opts: SimulationOptions) -> None:
346354
self._ensure_xll_registered()
347355
try:
348356
options_2d = [opts.to_string_list()] # 1 row x N cols
357+
# Persist options (esp. SeedFixed/seed0) to the workbook FIRST so the
358+
# per-cell-twister Manual-Seed engine actually honors the seed. See
359+
# _CMD_SET_SIM_OPTS above for the full rationale (AB#2742).
360+
app.api.Run(_CMD_SET_SIM_OPTS, options_2d)
349361
app.api.Run(_CMD_START_SIM, options_2d)
350362
except Exception as exc:
351363
raise SimulationFailedError(

0 commit comments

Comments
 (0)