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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
//! Per-component export: the independent sub-problems of `reduced.cnf`, each
//! with its own CNF and its own vtree.
//!
//! # Why this file exists
//!
//! The reduced formula splits into independent components, each compiled on
//! its own smaller vtree. The grafted whole-formula vtree carries no record of
//! where the split was — this manifest is that record.
//!
//! # Variable numbering — read this before using any file here
//!
//! Three spaces are in play, and mixing them silently produces a wrong count:
//!
//! 1. **ORIGINAL** — the input CNF's own 1-based DIMACS ids. Nothing in this
//! module is in that space; compose with `preprocess.json`'s
//! `reduced_to_original_dimacs` to get there.
//! 2. **REDUCED** — 1-based DIMACS ids of `reduced.cnf`, the formula the split
//! was computed on. [`ComponentsManifest::free_vars_reduced_dimacs`] and the
//! values of [`ComponentEntry::local_to_reduced_dimacs`] are in this space.
//! 3. **LOCAL** — each component is renumbered to a dense `1..=num_vars` space of
//! its own. `components/compNNN.cnf` is written in it, and that component's
//! vtree is built over it. [`ComponentEntry::local_to_reduced_dimacs`] is the
//! correspondence: `local_to_reduced_dimacs[i - 1]` is the REDUCED id of
//! LOCAL variable `i` (1-based on both sides), strictly increasing, so LOCAL
//! order is REDUCED order — but **the ids are not equal**, and assuming they
//! are is the failure this file exists to prevent.
//!
//! A component's own vtree file follows the same rule as the whole-formula
//! one: `compNNN.vtree` (standard SDD text) is LOCAL 1-based, numbering the
//! same variables as `compNNN.cnf`.
//!
//! # How the counts compose
//!
//! The components are variable-disjoint and clause-disjoint by construction, so
//! for a plain model count:
//!
//! ```text
//! count(reduced) = 2^|free_vars_reduced_dimacs| * Π_c count(compNNN.cnf)
//! ```
//!
//! and `count(original) = count(reduced) * 2^count_lift_pow2` from
//! `preprocess.json` closes the loop. A free variable occurs in no clause of
//! `reduced.cnf` — it belongs to no component, which is why the factor exists.
//!
//! For a projected count the free-variable factor is over the free variables
//! that are also show variables — a projected-out free variable contributes ×1,
//! not ×2:
//!
//! ```text
//! count_proj(reduced) = 2^|free_vars_reduced_dimacs ∩ show| * Π_c count_proj(compNNN.cnf)
//! ```
//!
//! where each component's own show set is [`ComponentEntry::show_vars_local_dimacs`]
//! (also written as a `c p show` line inside `compNNN.cnf`).
use PathBuf;
use crateCandidateRankMetric;
use crate;
use crateVtree;
use ;
use crateVtreeScores;
pub use write_components;
/// Manifest file name inside an output bundle directory.
pub const COMPONENTS_JSON_NAME: &str = "components.json";
/// Sub-directory holding the per-component CNF and vtree files.
pub const COMPONENTS_DIR: &str = "components";
/// Sub-directory holding the runner-up vtrees of each component's candidate
/// set — rank 0 is the selected vtree, and its entry points back at the
/// component's own vtree files rather than a byte-identical copy.
pub const CANDIDATES_DIR: &str = "candidates";
/// Format tag written into every [`ComponentsManifest`]; bump when a field is
/// added, removed, or changes meaning, and a consumer should refuse a tag it
/// does not know.
pub const COMPONENTS_FORMAT_TAG: &str = "vitri-components-v2";
/// One entry of a component's ranked candidate set: a vtree the portfolio
/// built and scored on its way to picking a winner.
///
/// Every candidate is a complete, usable vtree over the component's LOCAL
/// space, the same contract as the component's own vtree file — offered
/// because "best" is measured by this crate's own cost model
/// ([`scores`](Self::scores)); a consumer with a different one may prefer
/// another candidate.
///
/// The array's order is the rank: entry 0 is always the selected vtree, the
/// one the portfolio chose and the one the component's own `vtree` contains.
/// The rest are ordered by [`ComponentsManifest::candidate_rank_metric`], best
/// first.
///
/// Entry 0 is pinned rather than sorted into place because selection is not a
/// plain argmin over that metric — several candidates carry adoption rules that
/// also weigh peak width or a cost proxy, so the winner is occasionally not
/// the metric's own minimum.
/// Summary of the tree decomposition a component's vtree was converted from.
///
/// A summary, not the decomposition: the conversion's per-variable bag
/// assignment is proportional to the component and has no consumer outside the
/// build that produced it, so only these two numbers are published. Both are
/// measured on the graph projection the winning construction ran on (primal or
/// incidence), which is why a component can carry a `treewidth` at or above its
/// own variable count.
/// Which construction produced a component's vtree, and what it knew about the
/// decomposition behind it.
///
/// The vtree file says where every variable ended up but not what put it
/// there, and a candidate set is retained only on request — so for most
/// bundles this is the only record of the choice.
/// One independent sub-problem of `reduced.cnf`.
///
/// **All ids here are 1-based DIMACS** — see the module docs for which space
/// each field lives in.
/// The component split of `reduced.cnf`, written as `components.json`.
/// `#[serde(with = ...)]` for [`ComponentsManifest::candidate_rank_metric`]:
/// the metric is written as the [`VtreeScores`] field name
/// [`CandidateRankMetric::as_str`] gives it, `null` for absent.
/// Paths written by [`write_components`].
/// What [`write_components`] emits alongside the files it always writes.
/// Whether a manifest and the grafted whole-formula vtree describe the same
/// variable space: the components' reduced ids together with the free ones are
/// exactly `1..=whole.num_leaves()`, each named once.
///
/// The split PARTITIONS the reduced space, so naming one id twice and another
/// not at all is the failure to catch — a component mapped through the wrong
/// offset does exactly that, and leaves the totals matching. Asserted by
/// [`VitriRun::write_to_dir`](crate::VitriRun::write_to_dir) on the manifest it
/// just wrote against the vtree beside it.
pub