Stopping Rule Testing and Conformance
Purpose
This spec defines the conformance test suite for the StoppingRule enum, its five variants (IterationLimit, TimeLimit, BoundStalling, SimulationBased, GracefulShutdown), and the StoppingRuleSet composition layer, as specified in Stopping Rule Trait. The suite verifies that each individual rule evaluates correctly against hand-constructed MonitorState snapshots, that the composition layer correctly implements OR (“any”) and AND (“all”) modes with proper reason reporting, and that the bound stalling formula produces correct trigger/no-trigger decisions on concrete bound sequences. All test cases use small input sets (5-10 iteration histories) so that expected outputs can be verified by manual calculation using the formulas from Stopping Rules SS4-SS5.
Test cases reference the individual rule contracts from Stopping Rule Trait SS2, the composition contract from Stopping Rule Trait SS3, and the mathematical definitions from Stopping Rules.
SS1. Individual Rule Conformance Tests
Test naming convention: test_stopping_{rule}_{scenario} where {rule} is iteration_limit, time_limit, bound_stalling, simulation_based, or graceful_shutdown, and {scenario} describes the test case.
Shared MonitorState fixture: Unless otherwise noted, tests construct a MonitorState with the following baseline values. Individual tests override specific fields as needed.
| Field | Baseline Value |
|---|---|
iteration | 10 |
wall_time_seconds | 120.0 |
lower_bound | 1000.0 |
lower_bound_history | [900, 920, 940, 955, 970, 980, 988, 993, 997, 1000] (10 entries) |
shutdown_requested | false |
simulation_costs | None |
Note: The code’s
MonitorStatecarries a singlesimulation_costs: Option<Vec<f64>>field representing the current snapshot. The convergence monitor manages the two-snapshot comparison (current vs. previous) externally, storing the previous costs outsideMonitorState. Test cases in SS1.4 that require two snapshots supply them via the monitor’s external state, not through separateMonitorStatefields.
SS1.1 IterationLimit Conformance
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_iteration_limit_triggers_at_limit | IterationLimit { limit: 10 }. MonitorState with iteration=10. | Triggers. Reason: IterationLimit. The condition is satisfied (10 >= 10). |
test_stopping_iteration_limit_no_trigger_below_limit | IterationLimit { limit: 10 }. MonitorState with iteration=9. | Does not trigger. The condition is not satisfied (9 < 10). |
test_stopping_iteration_limit_triggers_above_limit | IterationLimit { limit: 10 }. MonitorState with iteration=15. | Triggers. Reason: IterationLimit. The condition is satisfied (15 >= 10). Monotonicity: once triggered, remains triggered. |
test_stopping_iteration_limit_edge_limit_one | IterationLimit { limit: 1 }. MonitorState with iteration=1. | Triggers. Reason: IterationLimit. Edge case: limit=1 triggers on the very first iteration (1 >= 1). |
SS1.2 TimeLimit Conformance
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_time_limit_triggers_at_threshold | TimeLimit { seconds: 120.0 }. MonitorState with wall_time_seconds=120.0. | Triggers. Reason: TimeLimit. The condition is satisfied (120.0 >= 120.0). |
test_stopping_time_limit_no_trigger_below | TimeLimit { seconds: 120.0 }. MonitorState with wall_time_seconds=119.9. | Does not trigger. The condition is not satisfied (119.9 < 120.0). |
test_stopping_time_limit_triggers_above | TimeLimit { seconds: 60.0 }. MonitorState with wall_time_seconds=120.0. | Triggers. Reason: TimeLimit. The condition is satisfied (120.0 >= 60.0). Monotonicity: wall-clock time only increases. |
SS1.3 BoundStalling Conformance
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_bound_stalling_triggers | BoundStalling { iterations: 5, tolerance: 0.01 }. MonitorState with iteration=10, lower_bound=1000.0, lower_bound_history=[900, 920, 940, 955, 970, 980, 988, 993, 997, 1000]. Bound at k-5 (index 5, 0-based) = 980. . | Does not trigger. . The improvement over the last 5 iterations exceeds the tolerance. |
test_stopping_bound_stalling_triggers_small_improvement | BoundStalling { iterations: 5, tolerance: 0.01 }. MonitorState with iteration=10, lower_bound=1000.0, lower_bound_history=[900, 920, 940, 955, 970, 992, 995, 997, 999, 1000]. Bound at k-5 (index 5, 0-based) = 992 (modified). . | Triggers. Reason: BoundStalling. . The improvement has fallen below the tolerance. |
test_stopping_bound_stalling_not_enough_history | BoundStalling { iterations: 5, tolerance: 0.01 }. MonitorState with iteration=3, lower_bound=940.0, lower_bound_history=[900, 920, 940]. | Does not trigger. Precondition not met: iteration (3) < iterations (5). Not enough history to evaluate the windowed comparison. |
test_stopping_bound_stalling_edge_window_one | BoundStalling { iterations: 1, tolerance: 0.001 }. MonitorState with iteration=10, lower_bound=1000.0, lower_bound_history=[…, 997, 1000] (last two entries). Bound at k-1 (index 8) = 997. . | Does not trigger. . Even with a 1-iteration window, the single-step improvement exceeds the tolerance. |
SS1.4 SimulationBased Conformance
The simulation-based rule requires mock MonitorState fields for simulation costs. These tests verify the two-phase evaluation logic without running actual Monte Carlo simulations – the convergence monitor populates current_simulation_costs and last_simulation_costs before calling evaluate.
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_simulation_based_triggers_both_phases_pass | SimulationBased { replications: 100, period: 5, bound_window: 3, distance_tol: 0.01, bound_tol: 0.001 }. MonitorState with iteration=10. Bound at k-3 (index 6) = 988. Phase 1: ? No, 12 > 1.0 – Phase 1 fails. Corrected state: lower_bound_history=[…, 999.5, 999.7, 999.8, 999.9, 1000] with bound at k-3 = 999.7. Phase 1: ? Yes. Phase 2: last_simulation_costs=Some([500.0, 300.0, 200.0]), current_simulation_costs=Some([500.5, 300.1, 200.05]). Distance . | Triggers. Reason: SimulationBased. Both phases pass: bound is stable () and simulation distance is below tolerance (). |
test_stopping_simulation_based_no_trigger_phase1_fails | SimulationBased { replications: 100, period: 5, bound_window: 3, distance_tol: 0.01, bound_tol: 0.001 }. MonitorState with iteration=10. lower_bound_history with bound at k-3 = 980. Phase 1: ? No, 20 > 1.0. current_simulation_costs=None (monitor does not run simulations because Phase 1 failed). | Does not trigger. Phase 1 (bound stability) fails: the bound improvement over the window () exceeds the stability threshold (). No simulation is run. |
test_stopping_simulation_based_no_trigger_phase2_fails | SimulationBased { replications: 100, period: 5, bound_window: 3, distance_tol: 0.001, bound_tol: 0.001 }. MonitorState with iteration=10. Bound stable (bound at k-3 = 999.8, ). last_simulation_costs=Some([500.0, 300.0, 200.0]), current_simulation_costs=Some([520.0, 310.0, 195.0]). Distance . | Does not trigger. Phase 1 passes (), but Phase 2 fails: simulation distance () exceeds distance_tol (). The policy has not stabilized. |
test_stopping_simulation_based_no_trigger_not_checkpoint | SimulationBased { replications: 100, period: 5, bound_window: 3, distance_tol: 0.01, bound_tol: 0.001 }. MonitorState with iteration=7. iteration % period = 7 % 5 = 2 != 0. | Does not trigger. Current iteration (7) is not a multiple of period (5). The rule is only evaluated at checkpoint iterations. |
test_stopping_simulation_based_no_trigger_first_eval | SimulationBased { replications: 100, period: 5, bound_window: 3, distance_tol: 0.01, bound_tol: 0.001 }. MonitorState with iteration=5. Bound stable. current_simulation_costs=Some([500.0, 300.0, 200.0]). last_simulation_costs=None (first simulation evaluation – no previous results to compare). | Does not trigger. This is the first simulation evaluation (last_simulation_costs is None). The distance metric requires two consecutive snapshots. The first evaluation always returns false. |
test_stopping_simulation_based_edge_period_one | SimulationBased { replications: 100, period: 1, bound_window: 1, distance_tol: 0.01, bound_tol: 0.001 }. MonitorState with iteration=2. Bound at k-1 = 999.9, lower_bound=1000.0. Phase 1: . last_simulation_costs=Some([500.0, 300.0]), current_simulation_costs=Some([500.02, 300.01]). Distance . | Triggers. Reason: SimulationBased. Edge case: period=1 means the rule is checked every iteration. Both phases pass at iteration 2 (the second evaluation, so last_simulation_costs is available). |
SS1.5 GracefulShutdown Conformance
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_graceful_shutdown_triggers_when_signaled | GracefulShutdown. MonitorState with shutdown_requested=true. | Triggers. Reason: GracefulShutdown. The signal flag is set; training must terminate unconditionally. |
test_stopping_graceful_shutdown_no_trigger_no_signal | GracefulShutdown. MonitorState with shutdown_requested=false. | Does not trigger. The signal flag is not set; no external termination signal has been received. |
test_stopping_graceful_shutdown_monotonic | GracefulShutdown. MonitorState with shutdown_requested=true at k=5, then at k=6 (flag remains true). | Triggers at both k=5 and k=6. Monotonicity: once the signal flag is set, it is never cleared during a training run. |
SS2. Composition Tests
These tests verify the StoppingRuleSet::should_stop composition logic: OR mode (“any”) and AND mode (“all”), including the GracefulShutdown override behavior defined in Stopping Rule Trait SS3.4.
Test naming convention: test_stopping_composition_{mode}_{scenario} where {mode} is or, and, or shutdown_override.
Composition fixture: Unless otherwise noted, tests use a StoppingRuleSet containing two rules: IterationLimit { limit: 10 } and TimeLimit { seconds: 120.0 }. The baseline MonitorState is the same as in SS1.
SS2.1 OR Mode (“any”)
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_composition_or_single_trigger | Rules: [IterationLimit { limit: 10 }, TimeLimit { seconds: 300.0 }]. Mode: Any. MonitorState: iteration=10, wall_time_seconds=60.0. IterationLimit triggers (10 >= 10). TimeLimit does not trigger (60 < 300). | Triggers. Returns (true, Single(IterationLimit)). One rule triggered is sufficient in OR mode. |
test_stopping_composition_or_both_trigger | Rules: [IterationLimit { limit: 10 }, TimeLimit { seconds: 120.0 }]. Mode: Any. MonitorState: iteration=10, wall_time_seconds=120.0. Both rules trigger. | Triggers. Returns (true, Single(IterationLimit)). In OR mode, the first triggered rule (in configuration order) is reported. IterationLimit is first in the list. |
test_stopping_composition_or_none_trigger | Rules: [IterationLimit { limit: 20 }, TimeLimit { seconds: 300.0 }]. Mode: Any. MonitorState: iteration=10, wall_time_seconds=120.0. Neither rule triggers. | Does not trigger. Returns (false, None). No rule is satisfied; training continues. |
test_stopping_composition_or_mixed_iteration_stalling | Rules: [IterationLimit { limit: 10 }, BoundStalling { iterations: 5, tolerance: 0.01 }]. Mode: Any. MonitorState: iteration=10, lower_bound=1000.0, lower_bound_history=[900, 920, 940, 955, 970, 980, 988, 993, 997, 1000]. IterationLimit triggers (10 >= 10). BoundStalling: , does not trigger. | Triggers. Returns (true, Single(IterationLimit)). In OR mode, IterationLimit alone is sufficient. BoundStalling not triggering is irrelevant. |
test_stopping_composition_or_second_rule_triggers_first | Rules: [IterationLimit { limit: 20 }, TimeLimit { seconds: 60.0 }]. Mode: Any. MonitorState: iteration=10, wall_time_seconds=120.0. IterationLimit does not trigger (10 < 20). TimeLimit triggers (120 >= 60). | Triggers. Returns (true, Single(TimeLimit)). The first triggered rule in configuration order is TimeLimit (the second configured rule), because IterationLimit did not trigger. |
SS2.2 AND Mode (“all”)
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_composition_and_all_trigger | Rules: [IterationLimit { limit: 10 }, TimeLimit { seconds: 120.0 }]. Mode: All. MonitorState: iteration=10, wall_time_seconds=120.0. Both rules trigger. | Triggers. Returns (true, Multiple([IterationLimit, TimeLimit])). All configured rules are satisfied simultaneously. |
test_stopping_composition_and_one_not_triggered | Rules: [IterationLimit { limit: 10 }, TimeLimit { seconds: 300.0 }]. Mode: All. MonitorState: iteration=10, wall_time_seconds=120.0. IterationLimit triggers (10 >= 10). TimeLimit does not trigger (120 < 300). | Does not trigger. Returns (false, None). In AND mode, all rules must trigger. TimeLimit is not satisfied, so training continues despite IterationLimit being satisfied. |
test_stopping_composition_and_none_trigger | Rules: [IterationLimit { limit: 20 }, TimeLimit { seconds: 300.0 }]. Mode: All. MonitorState: iteration=10, wall_time_seconds=120.0. Neither rule triggers. | Does not trigger. Returns (false, None). |
test_stopping_composition_and_mixed_iteration_stalling | Rules: [IterationLimit { limit: 10 }, BoundStalling { iterations: 5, tolerance: 0.01 }]. Mode: All. MonitorState: iteration=10, lower_bound=1000.0, lower_bound_history=[900, 920, 940, 955, 970, 980, 988, 993, 997, 1000]. IterationLimit triggers (10 >= 10). BoundStalling: , does not trigger. | Does not trigger. Returns (false, None). In AND mode, both rules must trigger. BoundStalling is not satisfied, so training continues despite IterationLimit being satisfied. |
test_stopping_composition_and_three_rules_all_trigger | Rules: [IterationLimit { limit: 10 }, TimeLimit { seconds: 60.0 }, BoundStalling { iterations: 5, tolerance: 0.05 }]. Mode: All. MonitorState: iteration=10, wall_time_seconds=120.0. BoundStalling: , triggers. All three rules trigger. | Triggers. Returns (true, Multiple([IterationLimit, TimeLimit, BoundStalling])). All three configured rules are reported in the stop reason. |
SS2.3 GracefulShutdown Override
These tests verify that GracefulShutdown bypasses the composition logic entirely, as specified in Stopping Rule Trait SS3.4.
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_composition_shutdown_override_or_mode | Rules: [IterationLimit { limit: 20 }, TimeLimit { seconds: 300.0 }]. Mode: Any. MonitorState: iteration=5, wall_time_seconds=30.0, shutdown_requested=true. Neither configured rule triggers. | Triggers. Returns (true, Single(GracefulShutdown)). The shutdown signal overrides the composition logic. No configured rule needs to trigger. |
test_stopping_composition_shutdown_override_and_mode | Rules: [IterationLimit { limit: 20 }, TimeLimit { seconds: 300.0 }]. Mode: All. MonitorState: iteration=5, wall_time_seconds=30.0, shutdown_requested=true. Neither configured rule triggers. | Triggers. Returns (true, Single(GracefulShutdown)). Even in AND mode, the shutdown signal causes immediate termination without requiring all rules to trigger. |
test_stopping_composition_shutdown_not_requested_and_mode | Rules: [IterationLimit { limit: 20 }, TimeLimit { seconds: 300.0 }]. Mode: All. MonitorState: iteration=5, wall_time_seconds=30.0, shutdown_requested=false. Neither configured rule triggers. | Does not trigger. Returns (false, None). Without a shutdown signal, normal AND mode logic applies, and neither rule is satisfied. |
SS3. Bound Stalling Numerical Tests
These tests verify the BoundStalling windowed relative improvement formula against specific bound sequences. Each test provides a complete lower bound history, the window size , the tolerance, and the hand-computed result at each relevant iteration.
Formula under test (Stopping Rules SS4, Stopping Rule Trait SS2.3):
Stopping condition: .
Test naming convention: test_stopping_bound_stalling_numerical_{scenario}.
SS3.1 Monotonically Increasing Bounds with Decreasing Increments
Bound sequence: LB = [100, 105, 108, 110, 111, 111.5, 111.8, 111.9, 111.95, 111.98]
Parameters: iterations (window) = 5, tolerance = 0.01.
Hand computation at each iteration where evaluation is possible ():
| Iteration | ? | Result | ||||
|---|---|---|---|---|---|---|
| 5 | 111.0 | 100.0 | 11.0 / 111.0 = 0.0991 | 0.0991 | No | Does not trigger |
| 6 | 111.5 | 105.0 | 6.5 / 111.5 = 0.0583 | 0.0583 | No | Does not trigger |
| 7 | 111.8 | 108.0 | 3.8 / 111.8 = 0.0340 | 0.0340 | No | Does not trigger |
| 8 | 111.9 | 110.0 | 1.9 / 111.9 = 0.0170 | 0.0170 | No | Does not trigger |
| 9 | 111.95 | 111.0 | 0.95 / 111.95 = 0.00849 | 0.00849 | Yes | Triggers |
| 10 | 111.98 | 111.5 | 0.48 / 111.98 = 0.00429 | 0.00429 | Yes | Triggers |
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_bound_stalling_numerical_decreasing_increments_k8 | BoundStalling { iterations: 5, tolerance: 0.01 }. MonitorState at iteration=8 with lower_bound=111.9 and lower_bound_history as above. . | Does not trigger. . The improvement over the last 5 iterations is still above the tolerance. |
test_stopping_bound_stalling_numerical_decreasing_increments_k9 | Same rule. MonitorState at iteration=9 with lower_bound=111.95. . | Triggers. Reason: BoundStalling. . The improvement has fallen below the tolerance. |
test_stopping_bound_stalling_numerical_decreasing_increments_k10 | Same rule. MonitorState at iteration=10 with lower_bound=111.98. . | Triggers. Reason: BoundStalling. . The bound continues to plateau; increments remain below tolerance. |
SS3.2 Flat Bounds
Bound sequence: LB = [500.0, 500.0, 500.0, 500.0, 500.0, 500.0]
Parameters: iterations (window) = 3, tolerance = 0.001.
Hand computation:
| Iteration | ? | Result | ||||
|---|---|---|---|---|---|---|
| 3 | 500.0 | 500.0 | 0.0 / 500.0 = 0.0 | 0.0 | Yes | Triggers |
| 4 | 500.0 | 500.0 | 0.0 / 500.0 = 0.0 | 0.0 | Yes | Triggers |
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_bound_stalling_numerical_flat_bounds | BoundStalling { iterations: 3, tolerance: 0.001 }. MonitorState at iteration=3 with lower_bound=500.0 and lower_bound_history=[500.0, 500.0, 500.0]. . | Triggers. Reason: BoundStalling. . Completely flat bounds trigger immediately once the window has enough history. |
SS3.3 Oscillating Bounds
Bound sequence: LB = [100.0, 102.0, 101.0, 103.0, 101.5, 104.0, 102.0, 105.0]
This sequence oscillates but trends upward. The oscillation means the windowed improvement alternates between positive and negative values, but the absolute relative improvement may exceed the tolerance on some iterations even though the trend is slowing.
Parameters: iterations (window) = 2, tolerance = 0.005.
Hand computation:
| Iteration | ? | Result | ||||
|---|---|---|---|---|---|---|
| 2 | 101.0 | 100.0 | 1.0 / 101.0 = 0.00990 | 0.00990 | No | Does not trigger |
| 3 | 103.0 | 102.0 | 1.0 / 103.0 = 0.00971 | 0.00971 | No | Does not trigger |
| 4 | 101.5 | 101.0 | 0.5 / 101.5 = 0.00493 | 0.00493 | Yes | Triggers |
| 5 | 104.0 | 103.0 | 1.0 / 104.0 = 0.00962 | 0.00962 | No | Does not trigger |
| 6 | 102.0 | 101.5 | 0.5 / 102.0 = 0.00490 | 0.00490 | Yes | Triggers |
| 7 | 105.0 | 104.0 | 1.0 / 105.0 = 0.00952 | 0.00952 | No | Does not trigger |
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_bound_stalling_numerical_oscillating_k3 | BoundStalling { iterations: 2, tolerance: 0.005 }. MonitorState at iteration=3 with lower_bound=103.0, lower_bound_history=[100.0, 102.0, 101.0, 103.0]. . | Does not trigger. . The 2-iteration window captures a positive jump from 102.0 to 103.0 that exceeds the tolerance. |
test_stopping_bound_stalling_numerical_oscillating_k4 | Same rule. MonitorState at iteration=4 with lower_bound=101.5, lower_bound_history=[100.0, 102.0, 101.0, 103.0, 101.5]. . | Triggers. Reason: BoundStalling. . The window from k-2=101.0 to k=101.5 shows only a 0.5 improvement, below tolerance. |
test_stopping_bound_stalling_numerical_oscillating_k5 | Same rule. MonitorState at iteration=5 with lower_bound=104.0, lower_bound_history=[100.0, 102.0, 101.0, 103.0, 101.5, 104.0]. . | Does not trigger. . The BoundStalling rule is non-monotonic: it triggered at k=4 but does not trigger at k=5 because a large bound jump reset the improvement above tolerance. |
SS3.4 Near-Zero Bounds
Bound sequence: LB = [0.0, 0.1, 0.2, 0.25, 0.28]
This sequence tests the denominator guard for near-zero bound values.
Parameters: iterations (window) = 2, tolerance = 0.05.
Hand computation:
| Iteration | ? | Result | |||||
|---|---|---|---|---|---|---|---|
| 2 | 0.2 | 0.0 | 1.0 (guard active) | 0.2 / 1.0 = 0.2 | 0.2 | No | Does not trigger |
| 3 | 0.25 | 0.1 | 1.0 (guard active) | 0.15 / 1.0 = 0.15 | 0.15 | No | Does not trigger |
| 4 | 0.28 | 0.2 | 1.0 (guard active) | 0.08 / 1.0 = 0.08 | 0.08 | No | Does not trigger |
| Test Name | Input Scenario | Expected Observable Behavior |
|---|---|---|
test_stopping_bound_stalling_numerical_near_zero_bounds | BoundStalling { iterations: 2, tolerance: 0.05 }. MonitorState at iteration=4 with lower_bound=0.28, lower_bound_history=[0.0, 0.1, 0.2, 0.25, 0.28]. . Denominator: . | Does not trigger. . The guard prevents the denominator from being 0.28 (which would inflate the relative improvement to ), using 1.0 instead for numerical safety. |
Cross-References
- Stopping Rule Trait – Enum definition (SS1.1), composition layer (SS1.2), individual rule contracts for IterationLimit (SS2.1), TimeLimit (SS2.2), BoundStalling (SS2.3), SimulationBased (SS2.4), GracefulShutdown (SS2.5), composition semantics for Any mode (SS3.2), All mode (SS3.3), GracefulShutdown override (SS3.4), MonitorState fields (SS4.3), validation rules V1-V10 (SS4.1)
- Stopping Rules (Math) – Iteration limit formula (SS2), time limit formula (SS3), bound stalling formula (SS4), simulation-based two-phase algorithm (SS5), combination logic (SS6)
- Convergence Monitoring – Convergence monitor architecture (SS2), tracked quantities (SS2.1), bound stalling detection (SS2.2), convergence evaluation protocol (SS2.3), per-iteration output record (SS2.4)
- Extension Points – Variant selection pipeline (SS6) where
StoppingRuleConfigis validated and converted, dispatch mechanism analysis (SS7), cross-variant composition rules (SS8.1, rule X2: CVaR + simulation stopping) - Backend Testing – Conformance test suite structure and requirements table format (reference pattern for this spec)
- Risk Measure Testing – Sibling conformance test spec following the same structure (shared fixture, conformance tables, numerical properties)
- Horizon Mode Testing – Sibling conformance test spec following the same structure
- Sampling Scheme Testing – Sibling conformance test spec following the same structure
- Cut Selection Testing – Sibling conformance test spec following the same structure (shared fixture with hand-computed values, aggressiveness ordering tests)
- Training Loop – Iteration lifecycle (SS2.1) where convergence update and stopping rule evaluation occur (step 5), termination conditions (SS2.2)
- Upper Bound Evaluation – Monte Carlo simulation for upper bound estimation, consumed by the simulation-based stopping rule’s Phase 2 comparison