1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/env python
"""E49 (Round 7 Task 7): the containment operator under the 4x clock.
Chimney's contained fraction fell to 0.56-0.59 under both E30b arms
(E33: 0.94) while IoU rose; Task 7's first job is to read the operator
and `SMC_STEPS_SCALE` handling and write down which constants (period,
growth-rate window, any per-tick rate constant) do and do not scale with
the clock.
Audit result (`validation/experiments/53-e49-containment-under-4x-clock.md`,
constants table): everything the containment operator itself reads
scales with `SMC_STEPS_SCALE`, either automatically (`period_steps` via
`steps_per_day_from`; the growth window, since it is exactly one period
regardless of tick count; `tau_days`'s decay, computed from hours, not
ticks; `SMC_ASSIM_EVERY`/`SMC_MAX_DAYS`, both counted in observation
windows/days, never steps) or by construction (`model.burn_duration`'s
gene range, widened from [5, 20] to [20, 80] -- exactly x4 -- in
`priors/arrival_x4.json`, the prior Arm B already uses). The `.max(1e-4)`
growth floor is dimensionless (a fraction of a period's own growth, not a
tick count) and needs no clock-dependent scaling at all. So this runner
takes the **sweep** branch (TEST_PLAN.md v1.9 E49, "if everything scales:
... sweep the containment threshold on the four calibration fires only").
Two arms, `--arm {fix, sweep}`, one arm per batch, same restructuring
Tasks 5/6 used for `exp_r7_e45.py`/`exp_r7_e46.py`:
- `--arm fix` (branch 2, NOT launched -- kept only so the pre-registered
branch this task's original placeholder declared stays buildable and
documented; the audit found no scaling bug to fix). Chimney seeds 0-4,
Arm B preset unchanged, 5 runs. Output
exp49_containment_4x_clock_fix.json.
- `--arm sweep` (branch 3, the one actually launched). Four calibration
fires only (Bear, Brattain, Buck, Chimney -- Ferguson and Pier are
holdout and stay untouched by this experiment), seed 0, three values
of the new opt-in `SMC_CONTAIN_GROWTH_FLOOR` knob
(`cella_lib::wildfire::driver::WildfireDriver::contain_growth_floor`,
`default_contain_growth_floor() == 1e-4`, unset/default byte-identical
to every report before this knob existed): 1e-5, 1e-4 (baseline), 1e-3
-- one order of magnitude below and above the operator's own
pre-existing hard-coded floor. This is the operator's one genuine
scalar "threshold" (a floor `period_end` applies to a period's growth
ratio before its `ln`, in the containment logit `a + b*ln(growth)`);
`GENE_CONTAIN_A`/`GENE_CONTAIN_B` are free genes the filter fits per
member, not a single fixed threshold value a sweep could hold
constant, so they are not the sweep target. Arm B preset otherwise
unchanged (arrival kernel, rear-focus wind law, 4x clock, arrival_x4
prior, wind-rotation gene). 4 fires x 3 values = 12 runs. Output
exp49_containment_4x_clock_sweep.json.
- `--arm diag` (Phase 2, added after the sweep came back byte-identical
across all three floors on every fire). ONE job: Bear seed 0, Arm B
preset, `SMC_CONTAIN_GROWTH_FLOOR=1e-3` (the sweep's widest floor),
`SMC_DIAG=1`, which records every daily containment draw (burned count
before/after, raw growth ratio before the floor, the member's
contain_a/contain_b, outcome) in each window's `diag.contain_draws`.
Since the three sweep runs were identical, this one run's draws are the
draws of all three, and they say whether any floor in {1e-5, 1e-4,
1e-3} could have changed any draw. The report's own
`contain_growth_floor` field shows the floor the driver held. Output
exp49_containment_4x_clock_diag.json.
Prediction (write before the run, TEST_PLAN.md v1.9 verbatim): the
fraction is correct, not a bug -- the fire really grows for longer under
the faster clock, and the ICS-209 lead E42 measured (13-21 days) shrinks
toward the real 5-10 days. (Checked in Phase 2 against E44's own Chimney
Arm B reports -- already on disk, no re-run needed for that clause.)
`--dry-run` (with or without `--arm`) prints jobs and the exact
command/env for the selected arm(s); launches nothing. `--arm sweep` is
the only arm this task actually runs; `--arm fix` is provided for
completeness/documentation only, per the branch decision above.
"""
# noqa: E402
=
=
# The operator's own pre-existing hard-coded value (`default_contain_growth_floor`
# in `cella_lib/src/wildfire/driver.rs`) sits in the middle, one order of
# magnitude away from each swept endpoint.
=
=
"""Branch 2 (NOT launched -- see module docstring). Chimney seeds 0-4,
Arm B preset unchanged; kept buildable/dry-runnable only."""
return
"""Branch 3 (the one actually launched). Four calibration fires, seed
0, three SMC_CONTAIN_GROWTH_FLOOR values, Arm B preset otherwise
unchanged."""
return
"""Phase 2 diagnostic: one job, Bear seed 0, the sweep's widest floor
(1e-3), SMC_DIAG=1 so every containment draw is recorded."""
return
=
=
=
=
=
=