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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
//! Time-budget helpers: scale internal sub-budgets as a fraction of the
//! per-run wall-clock hint.
//!
//! Every function here is pure. The hint itself is
//! [`RunConfig::budget_ms`](crate::config::RunConfig::budget_ms), carried to
//! each site as an argument — preprocessing passes
//! [`RunConfig::effective_budget_ms`](crate::config::RunConfig::effective_budget_ms)
//! into the stage that needs it, and vtree construction carries it in the
//! build limits `component::build_vtree` assembles. A run with no budget is
//! deliberately unbounded, not accidentally so.
/// How long there is until `deadline` — zero once it has passed, which every
/// consumer reads as "stop now" rather than as "unbounded".
///
/// "Now" is [`crate::decompose::meter::now`], which is the real clock unless a
/// deterministic construction budget is in force. That is what converts every
/// deadline expression in vtree construction at once: the deadlines themselves
/// are unchanged, and the clock they are compared against is the one the
/// budget named.
pub
/// Whether `deadline` has passed. `None` is the unbounded run — it never
/// expires, the same reading of an absent deadline every function here takes.
///
/// The one spelling of the check, so a caller cannot ask the question in a way
/// that treats "no deadline" as "out of time".
pub
/// One item's share of a `deadline` several items divide, as a deadline of
/// its own: `weight` out of `total_weight` of whatever time is left.
///
/// Pro-rata rather than even — an even split starves the one big item in a set
/// of otherwise tiny ones. The share is counted BACK from the shared deadline
/// rather than forward from a second clock reading, so an item's deadline is
/// never later than the one it divides, and re-reading the clock per item rolls
/// unspent time forward to the ones that follow.
///
/// Once `deadline` has passed the share is zero and the work receiving it
/// starts already expired, which is the caller's signal to stop.
pub
/// A stage budget cut down to what the run has left. Without a deadline the
/// stage keeps its own budget: a run with no deadline is deliberately unbounded.
pub
/// The budget-scaling rule every sub-budget below is expressed in: a fraction
/// of the hint, clamped, with an absolute default when there is no hint.
pub
/// The floor is low because preprocessing that runs out of time keeps whatever
/// Arjun completed within the window and records it — partial output is
/// still usable.
///
/// The short-window ratio was 1/6 as of the measurement below; it was 1/12
/// before that. 1/12 was tuned for a consumer whose single downstream stage
/// owned the rest of the wall, so every second spent reducing was a second that
/// stage lost. A consumer that slices its wall into several attempts is buying a
/// shorter first attempt and the attempts behind it, and 1/6 is the
/// corresponding re-sizing. Measured on a model-counting-competition board at a
/// two-minute timeout: +7 net solved instances, no count regressions. The 1/4
/// long-window branch, the clamp and the no-hint default were not part of that
/// change.
pub
const ARJUN_BUDGET_ABS_DEFAULT_MS: u64 = 600_000;
const ARJUN_BUDGET_FLOOR_MS: u64 = 5_000;
const ARJUN_BUDGET_CAP_MS: u64 = 600_000;
/// Wall-clock SAFETY NET for vtree CONSTRUCTION: how much of the remaining
/// per-CNF budget the whole portfolio candidate build (all candidates, all
/// components) may spend before it must hand back what it has.
/// `remaining_wall_ms` is the time left until the run's deadline at the moment
/// construction starts.
///
/// This is a SAFETY NET, not a tuning knob — deliberately generous:
/// - The ceiling of a single healthy candidate is well under the floor (goatd's
/// own doc measures its refinement loop at ~65 s worst case on a ~1k-var
/// formula), so no candidate that solves within this floor has its
/// construction truncated.
/// - The 90 s FLOOR keeps short budgets untouched: at a 120 s budget the budget is
/// the floor, which exceeds the budget itself, so the deadline is inert there
/// (the caller additionally clamps it to the run's deadline).
/// - The 900 s CAP is what actually bites: at an hour-long budget a pathological
/// build can otherwise spend most of it and hand the consumer nothing to
/// compile, so construction is cut to at most a quarter of the budget.
///
/// Enforcement is in the portfolio driver, and the deadline alone is not all of
/// it: the driver consults it between candidates, so a candidate that has
/// already started would otherwise run to completion however long it takes —
/// and that is the candidate which overruns the ceiling. Each candidate is
/// additionally capped at the time left when it starts
/// (`RunState::cand_wall_ms`).
///
/// The bound is soft, at the granularity of one FlowCutter restart iteration:
/// the vendored library checks its deadline between iterations rather than
/// inside one. Its two greedy pre-passes are abandoned at the deadline, but the
/// first multilevel partition of a build that holds no decomposition yet runs
/// unbounded, because returning nothing is worse than returning late.
///
/// No env override, by design: an escape hatch here would be a knob whose only
/// job is to disable a safety net. The individual construction knobs that feed
/// into how long a candidate takes (`vtree_effort_scale`,
/// `VITRI_GOATD_REFINE_BUDGET_MS`) keep their own overrides and compose with
/// this ceiling — a tighter goatd budget still wins, this only imposes a roof.
pub
const VTREE_BUDGET_FLOOR_MS: u64 = 90_000;
const VTREE_BUDGET_CAP_MS: u64 = 900_000;
/// The absolute deadline a DETERMINISTIC construction budget names: `units` of
/// work, converted to the milliseconds the deadline machinery is written in and
/// counted forward from `epoch`, the instant the meter was armed at.
///
/// The run's own deadline plays no part, and this is the one construction policy
/// of which that is true. A deadline anchored before preprocessing leaves
/// construction however much of the wall preprocessing happened not to use,
/// which differs run to run — exactly the dependence a deterministic budget
/// exists to remove. What bounds construction here is the work it is allowed to
/// do, and nothing else.
///
/// `None` where the platform's clock cannot represent an instant that far ahead
/// — a budget nothing could spend bounds nothing, and the meter's own clock
/// saturates the same way, so an unbounded construction is what both ends agree
/// on. Returning it beats panicking on a number a caller is free to pass.
pub
/// [`vtree_budget_ms`] as a deadline: the share of `run_deadline` construction
/// gets when it starts at `now`, never later than `run_deadline` itself.
///
/// The share policy alone. Which policy a run uses is
/// [`ConstructionBudget`](crate::config::ConstructionBudget)'s to say, and
/// [`RunConfig::construction_deadline`](crate::config::RunConfig::construction_deadline)
/// is where they are told apart — including the run with no deadline at all,
/// which never reaches here.
pub
/// Construction-effort multiplier for `budget_ms`, relative to a calibration
/// baseline timeout. `None` (unbounded) is the baseline, `1.0`.
///
/// Two consumers scale by it: the portfolio driver's FlowCutter step and
/// iteration counts, and the multilevel hypergraph bisector's restart and
/// V-cycle counts. Both are calibrated for a short budget (~90s), where every
/// second of construction is a second the caller does not get back. Under a
/// long budget construction is a small share of the run by comparison, so these
/// counts grow with the declared budget.
///
/// The sub-linear exponent avoids an hour-long budget demanding a linear 40×
/// blowup while still scaling with the declared budget.
pub
const EFFORT_BASELINE_MS: u64 = 90_000;
const EFFORT_EXPONENT: f64 = 0.5;
const EFFORT_SCALE_MIN: f64 = 1.0;
const EFFORT_SCALE_MAX: f64 = 8.0;