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
//! The projection-preserving chain: the stages that are sound when the
//! answer is a count over a SHOW set rather than over every variable.
//!
//! # Why the plain chain cannot be reused here
//!
//! [`crate::preprocess::simplify`]'s chain is count-preserving for plain model
//! counting only. Under a projection each of its stages has a way to be wrong:
//!
//! - **Definable-variable elimination** removes a variable whose value is a
//! function of the others. If that variable is a SHOW variable and the function
//! reads HIDDEN variables, two models agreeing on the rest of the show set can
//! still disagree on it — so removing it MERGES two distinct show-projections
//! into one and undercounts.
//! - **Equivalence substitution** eliminates one member of `x ≡ y`. Sound only if
//! the survivor is the SHOW variable; substituting a show variable by a hidden
//! representative leaves the show set naming a variable the formula no longer
//! has.
//! - **A free variable's `×2`** is a factor of 2 for a show variable and a factor
//! of ONE for a projected-out variable, which the shared `2^k` cannot tell
//! apart.
//!
//! So this module runs a different chain, each stage of which is ×1 for the
//! projected count:
//!
//! 1. [`strengthen_projected_hidden`] — the full DVE pipeline
//! FROZEN on the show set, so every variable it eliminates is hidden
//! (∃-absorbed, ×1). It may additionally prove a show variable equivalent to
//! another SHOW variable; the survivor is forced to be a counted variable and
//! the eliminated member is dropped from the show set (`pc(S) == pc(S\{elim})`,
//! still ×1). Those merges are returned so a WEIGHTED caller can fold the
//! eliminated member's literal weights into its survivor.
//! 2. **Projected BVE** — resolution variable elimination restricted to the
//! projected-out variables, which is clause-level ∃ and therefore exactly ×1.
//!
//! Both stages PRESERVE variable ids, so the show set, the weight tables and any
//! variable map established upstream (by an Arjun projection minimization) stay
//! valid across them without a second renumbering to compose.
//!
//! # The chain is the whole contract
//!
//! [`strengthen_and_bve`] takes a formula, a show set and the run's deadline and
//! nothing else: there is no stage parameter, no eliminator to supply, and no way
//! to reach [`crate::preprocess::simplify`]'s count-preserving stage list. The
//! projected and weighted-projected modes run the SAME two stages — the only
//! difference between them is what a caller does with
//! [`ProjectedReduction::folds`] afterwards
//! ([`Weights::fold_eliminated`](crate::cnf::Weights::fold_eliminated)).
use crate;
use cratebve_project;
/// What [`strengthen_and_bve`] produced.
pub
/// The strengthening stage's own ceiling. A ceiling rather than a spend: a run
/// whose deadline falls sooner stops at the deadline, and a run with no deadline
/// spends up to this.
const STRENGTHEN_BUDGET_MS: u64 = 3_000;
/// Run the projected reduction: hidden-variable strengthening, then
/// projected BVE.
///
/// `show_set` is the projection the answer is taken over. A caller with no show
/// set is doing plain counting and belongs on the count-preserving chain — every
/// projected entry point guarantees a non-missing show set before reaching here.
///
/// `deadline` is the run's, and it binds here as everywhere else: the
/// strengthening stage gets whatever is left of it, up to
/// [`STRENGTHEN_BUDGET_MS`].
pub
/// Projection-aware hidden-variable strengthening for the `pmc` and `pwmc`
/// chains.
///
/// Runs the full DVE pipeline (equivalence merge + resolution VE + definability
/// elimination + vivification) FROZEN on the show variables, so every variable
/// it eliminates is a HIDDEN (projected-out) variable. Each such elimination is
/// ×1 for the projected count: hidden vars are existentially quantified
/// (∃-absorbed), so resolution VE, definability elimination, AND equivalence
/// merge of a hidden var all preserve the projected count exactly — and, since
/// hidden vars are weight-1 in the projected-weighted fold, the projected-
/// weighted count too.
///
/// VarIds are preserved (`keep_original_vars=true`) so the result composes with
/// the id-preserving `bve_project` that runs after it and with the
/// `show`/`free_show` accounting in the projected chain.
///
/// Show vars are eliminated only in two accounted ways. `frozen=show` blocks
/// resolution VE and the definability loop from touching them, so the only
/// removals are:
/// 1. **Equivalence merge** (`FrozenEquiv::ForceShowRep`): when a show var is
/// equivalent to another counted var, the survivor is forced to be a show
/// var and the eliminated members are *determined* (×1). DVE gives them the
/// `Equiv` fate; the function returns them so the projected chain drops
/// them from the show set (counting only the survivor).
/// 2. **Genuine freeness**: a show var whose clauses are all redundant becomes
/// clauseless (the `Free` fate). It stays in the show set and the projected
/// chain's `×2` free-show factor is correct.
///
/// A backbone forcing a show var is NOT a removal: CaDiCaL vivification re-pins
/// every forced literal as a unit clause, so a forced show var stays present
/// (×1 at its forced value), never mis-marked free. A defensive guard still
/// rejects the result if any show var was eliminated by an unexpected path
/// (neither equiv nor free) — that would signal a `frozen`-protection leak.
///
/// Returns `(residual, determined_show)` where `determined_show` lists, for each
/// show var merged away as equivalent, an [`EquivFold`]. The survivor's
/// variable is ALWAYS a counted (show) var — `ForceShowRep` forces the SCC
/// representative to be a show var.
pub