Why Modular Architecture Matters
A MAMS simulation combines several different tasks: specifying the design and treatment effects, generating trial outcomes, applying interim rules, making final decisions, and estimating operating characteristics across replicates. If these tasks are interleaved in one large function, a change to one part can silently alter another. For example, changing how a scenario is represented could accidentally change the interim decision rule, or a summary function could count a dropped arm as a final success.
Modular architecture addresses this risk by giving each task a defined function, with explicit inputs and outputs. The objective is not to maximize the number of functions. It is to make important transitions visible: what information enters each stage, what information leaves it, and which part of the simulation is responsible for each rule. The earlier material on vectorized simulation is compatible with this approach: functions can process one replicate or a batch, provided their interfaces and returned records remain well defined.
A useful division is shown below. Names are illustrative; the contract and responsibilities matter more than the particular names.
| Function | Receives | Returns | Responsibility |
|---|---|---|---|
| Scenario generator | Design settings and a scenario specification | A validated scenario object | Combines design information with the treatment-effect configuration |
| Trial-data simulator | Scenario, replicate identifier, and random-number stream | Stage-level trial data | Generates outcomes and allocation records under the scenario |
| Interim decision function | Available stage data and decision-rule settings | Arm-level decisions and decision statistics | Applies pre-specified continuation or dropping rules |
| Final analysis function | Data available for final analysis and analysis settings | Final test results and decisions | Applies the stated final analysis to eligible arms |
| Summary function | Replicate-level results | Operating-characteristic estimates and uncertainty summaries | Aggregates trial outcomes across replicates |
Contracts and Data Flow
A scenario should distinguish design settings from scenario truth. Design settings describe the trial to be simulated: the control and treatment arms, stage structure, allocation ratios, sample sizes, and decision boundaries. Scenario truth describes the data-generating conditions, such as each arm’s treatment effect and the outcome variance. Keeping them separate makes it possible to evaluate one design under multiple plausible effect configurations without rewriting the trial logic.
The trial-data simulator should receive the scenario and a random-number stream explicitly. It should return the generated data, with enough structure to identify replicate, stage, arm, and allocation. A decision function should consume only data available at the relevant interim. It should return a decision record, not merely a reduced data set: for example, which arms continue, which stop, and the statistics used. Preserving that record supports later summaries and makes the sequence of decisions auditable.
The final analysis function should likewise state which arms are eligible for analysis and what data contribute to each analysis. Its output should distinguish a numerical test result from the final decision made using that result. Finally, the summary function should operate on replicate-level results, rather than trying to reconstruct trial histories from a few totals.
Validate the design settings and attach the specified treatment effects and nuisance parameters.
Use the scenario and an explicit random-number stream to generate stage- and arm-labelled outcomes.
Use only the data available at each interim, returning decisions and the statistics that support them.
Apply the specified final analysis to arms and data eligible under the decision history.
Aggregate decision records and trial-level quantities into operating-characteristic estimates.
Worked Examples of Function Boundaries
Worked Example: Building a Scenario Without Changing the Design
Suppose a hypothetical design has a control arm and two experimental arms, A and B, with two stages. Each arm enrolls 20 participants per stage, and outcomes are modeled as independent normal observations with known standard deviation \(\sigma=10\). Consider two scenario specifications: one with effects \(0\) for both experimental arms, and another with effects \(3\) and \(0\), in outcome units.
The scenario-generator contract can accept the same design settings in both calls and change only the effect vector. Its returned objects would therefore have the same arm labels, stage structure, allocation, and \(\sigma\), but different treatment-effect values. For example, the effect vector changes from \((0,0)\) to \((3,0)\). This separation means the data simulator can use one implementation for both scenarios.
The inputs should be checked before simulation. The effect vector must contain one value per experimental arm; the sample sizes must be positive; and the stage and arm labels must agree across design settings. If these conditions hold, the scenario generator returns a complete, internally consistent object. If an effect is accidentally assigned to the control arm or an arm label is missing, the generator should report the invalid specification rather than silently recycling or reordering values.
Worked Example: Returning an Interim Decision Record
Consider arm A versus control at the first interim in the hypothetical normal-outcome design above. Suppose each arm has 20 observations, the observed sample means are 13 for A and 11 for control, and \(\sigma=10\) is known. Under independent observations and the specified allocation, the standard error for the difference in means is
The observed standardized difference is
For this software illustration only, suppose the specified continuation rule retains an arm when its interim statistic is at least \(0.60\). Since \(0.632\geq0.60\), the decision function returns “continue” for arm A, along with the statistic \(0.632\), the stage label, and the rule applied. This toy threshold is not asserted to be a calibrated MAMS boundary. The decision function should not draw new outcomes or modify the threshold; those responsibilities belong to other components or to the input settings.
Worked Example: Summarizing Replicate-Level Results
Suppose a simulation produces 100 replicate records for a specified scenario. The final-analysis output indicates that at least one experimental arm met the defined success criterion in 7 replicates. The estimated probability of this event is the observed proportion:
Treating the replicate outcomes as independent Bernoulli observations, the Monte Carlo standard error of this estimated probability is
where \(R=100\) is the number of replicates. The summary function should calculate this from the event indicator and replicate count, not from a rounded percentage. If the replicate records also contain total sample sizes, the same function can report their mean separately; it should not infer sample size from the success count. This example estimates a particular event under one specified scenario, not a universal property of the design.
Why the Separation Preserves the Simulation
Modularization is useful only if the assembled functions still represent the intended simulation. The justification is a composition argument. Let \(S\) denote a validated scenario, \(U\) the random-number input, \(D\) the generated data, \(I\) the interim-decision record, and \(F\) the final-analysis record. The simulation computes these in order:
Here \(g,h,a,q\) are the data-generation, interim-decision, final-analysis, and summary operations. If each function implements its stated rule, receives the complete required inputs, and returns the information required by the next function, then composing them applies the same sequence of operations as the design specification. In particular, \(h\) cannot use future-stage observations if its input contains only interim-available data, and \(a\) cannot analyze an arm excluded by \(I\) if eligibility is explicitly passed and enforced. A function boundary does not itself guarantee correctness; it makes the required information and responsibility inspectable.
Diagnostics, Alternatives, and Limitations
Several symptoms indicate that the interfaces are not working well. A function that repeatedly looks up global variables has hidden inputs, so the same call may behave differently in different sessions. A decision function that both generates outcomes and drops arms mixes data generation with trial logic. A final summary that receives only overall totals cannot verify which replicate or arm produced an event. Inconsistent arm labels or stage identifiers can also cause apparently valid numbers to be attached to the wrong treatment.
A practical diagnostic is to trace one replicate from scenario to summary. At every boundary, check that the returned object contains the required identifiers, that the number of arms and stages is as expected, and that decision records agree with the data available at that point. Also check that the simulator uses the supplied random-number stream rather than an undocumented internal source. This makes reproducibility and debugging easier; the earlier material on reproducible random-number streams addresses how streams are constructed and managed.
A monolithic simulation can be faster to write for a small, one-off calculation, but its intertwined logic makes changes and review harder. At the other extreme, creating many tiny functions can obscure the flow and impose needless interface complexity. A useful alternative is a small set of cohesive modules with clear contracts, plus a top-level driver that coordinates them. Vectorized implementations may process many replicates in a batch, while retaining the same conceptual boundaries.
Modularity has limits. It does not establish that the statistical model is appropriate, that boundaries control error rates, or that a program correctly implements its specifications. It can also add overhead if data are copied repeatedly or interfaces are overgeneralized. Keep returned records informative enough to preserve trial history, but avoid storing large intermediate objects that are not needed for decisions, summaries, or review. Verification of interim logic and independent checks are separate tasks.
A common mistake is to treat a clean function structure as evidence that the simulation is statistically valid. For example, a well-named decision function may still use a final-stage observation at an interim, or a summary may count the wrong event. The correction is to define the statistical rule precisely, document when each input is available, and inspect the data and decision records at each interface. Clear architecture helps expose such errors; it does not replace checking the rules themselves.
Check Your Understanding
Use the function-contract principles to answer each question. Short solutions follow the questions.
- Which function should receive the random-number stream, and what should it return?
- Why should scenario truth and design settings be represented separately?
- A summary function receives only the total number of successes across all replicates. Name one useful piece of information it cannot recover from that total alone.
- For the worked interim example, what continuation decision follows from the stated toy rule and statistic?
Solutions. (1) The trial-data simulator receives the stream explicitly and returns stage- and arm-labelled trial data; the stream should not be hidden in global state. (2) Separating them allows the same design to be evaluated under different treatment-effect configurations without changing the trial logic. (3) It cannot recover which replicate succeeded, nor can it link outcomes to that replicate’s sample size or decision history. (4) The statistic is approximately \(0.632\), which exceeds \(0.60\), so arm A continues under the illustrative rule.