ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
BooleanCircuit.h
Go to the documentation of this file.
1/**
2 * @file BooleanCircuit.h
3 * @brief Boolean provenance circuit with support for knowledge compilation.
4 *
5 * @c BooleanCircuit represents the provenance formula of a query result
6 * as a Boolean circuit (AND/OR/NOT/IN gates). It supports multiple
7 * methods for computing the probability of the formula being true under
8 * tuple-independent probabilistic databases:
9 *
10 * | Method | Description |
11 * |------------------------|----------------------------------------------------------|
12 * | @c possibleWorlds() | Exact enumeration over all 2^n possible worlds |
13 * | @c compilation() | Knowledge compilation to d-DNNF via an external tool |
14 * | @c monteCarlo() | Monte Carlo sampling approximation |
15 * | @c WeightMC() | Weighted model counting via weightmc |
16 * | @c independentEvaluation() | Exact evaluation for disconnected circuits |
17 * | @c interpretAsDD() | Direct tree-decomposition-based compilation |
18 * | @c makeDD() | Generic d-DNNF construction dispatcher |
19 *
20 * ### Multivalued inputs (@c MULIN / @c MULVAR)
21 * Multivalued input gates model tuples drawn from a discrete probability
22 * distribution. @c rewriteMultivaluedGates() rewrites them into standard
23 * AND/OR/NOT circuits before knowledge compilation.
24 *
25 * The circuit is Boost-serialisable for transmission to external processes.
26 */
27#ifndef BOOLEAN_CIRCUIT_H
28#define BOOLEAN_CIRCUIT_H
29
30#include <unordered_map>
31#include <unordered_set>
32#include <set>
33#include <map>
34#include <vector>
35#include <iosfwd>
36
37#include <boost/archive/binary_oarchive.hpp>
38#include <boost/serialization/unordered_map.hpp>
39#include <boost/serialization/map.hpp>
40#include <boost/serialization/set.hpp>
41#include <boost/serialization/vector.hpp>
42
43#include "Circuit.hpp"
44
45/**
46 * @brief Gate types for a Boolean provenance circuit.
47 *
48 * - @c UNDETERMINED Placeholder for a gate whose type has not been set yet.
49 * - @c AND Logical conjunction of child gates.
50 * - @c OR Logical disjunction of child gates.
51 * - @c NOT Logical negation of a single child gate.
52 * - @c IN An input (variable) gate representing a base tuple.
53 * - @c MULIN A multivalued-input gate (one of several options).
54 * - @c MULVAR Auxiliary gate grouping all @c MULIN siblings.
55 */
56enum class BooleanGate {
57 UNDETERMINED, ///< Placeholder gate whose type has not been set yet
58 AND, ///< Logical conjunction of child gates
59 OR, ///< Logical disjunction of child gates
60 NOT, ///< Logical negation of a single child gate
61 IN, ///< Input (variable) gate representing a base tuple
62 MULIN, ///< Multivalued-input gate (one of several options)
63 MULVAR ///< Auxiliary gate grouping all MULIN siblings
64};
65class dDNNF;
66
67/**
68 * @brief d-DNNF certificate value for the (gate-type-specific) per-gate
69 * info field.
70 *
71 * On an OR (@c gate_plus) gate, info = @c DNNF_CERT_INFO asserts
72 * **determinism**: the children are mutually exclusive. On an AND
73 * (@c gate_times) gate it asserts **decomposability**: the children
74 * mention disjoint sets of variables. Both are semantic properties of
75 * the gate's (content-addressed) children, so a truthfully-set mark
76 * remains true however the gate is later re-derived. Set by
77 * constructions that establish the property structurally (the
78 * decomposition-aligned reachability compilation); trusted by
79 * @c independentEvaluation() and @c interpretAsDD(), the same trust
80 * model as the planner-asserted inversion-free certificate.
81 */
82constexpr unsigned DNNF_CERT_INFO = 1;
83
84/**
85 * @brief Boolean circuit for provenance formula evaluation.
86 *
87 * Inherits the gate/wire infrastructure from @c Circuit<BooleanGate> and
88 * adds probability annotation, info integers (for multivalued inputs), and
89 * a rich set of evaluation algorithms.
90 */
91class BooleanCircuit : public Circuit<BooleanGate> {
92private:
93/**
94 * @brief Evaluate the sub-circuit at @p g on one sampled world.
95 *
96 * Each gate in @p sampled is treated as @c true; all other @c IN gates
97 * are @c false.
98 *
99 * @param g Root gate to evaluate.
100 * @param sampled Set of input gates that are @c true in this world.
101 * @return Boolean value of the circuit at gate @p g.
102 */
103bool evaluate(gate_t g, const std::unordered_set<gate_t> &sampled) const;
104
105/**
106 * @brief Recursive helper for @c interpretAsDD().
107 *
108 * A certified (d-DNNF-marked) gate is handed to
109 * @c interpretCertifiedIsland(); the recursion only walks the
110 * uncertified region.
111 *
112 * @param g Current gate to process.
113 * @param seen Set of variable gates already consumed (read-once check in
114 * the uncertified region; per-island registration inside
115 * certified islands).
116 * @param dd The d-DNNF being constructed.
117 * @return Gate ID in @p dd corresponding to @p g.
118 */
119gate_t interpretAsDDInternal(gate_t g, std::set<gate_t> &seen, dDNNF &dd) const;
120/**
121 * @brief Iteratively copy a certified island into @p dd.
122 *
123 * Island twin of @c evaluateCertifiedIsland() (same discipline, same
124 * iterative post-order): certified ORs are copied as native
125 * deterministic ORs (no De Morgan rewriting), certified ANDs as-is,
126 * shared sub-DAGs map to shared @p dd gates. An uncertified AND/OR
127 * inside the island falls back to @c interpretAsDDInternal().
128 *
129 * @param root Certified gate the island is rooted at.
130 * @param seen Global variable registration (see
131 * @c evaluateCertifiedIsland()).
132 * @param dd The d-DNNF being constructed.
133 * @return Gate ID in @p dd corresponding to @p root.
134 */
135gate_t interpretCertifiedIsland(gate_t root, std::set<gate_t> &seen,
136 dDNNF &dd) const;
137/**
138 * @brief Recursive helper for @c independentEvaluation().
139 *
140 * A certified (d-DNNF-marked) gate is handed to
141 * @c evaluateCertifiedIsland(); the recursion only walks the
142 * uncertified region.
143 *
144 * @param g Current gate to evaluate.
145 * @param seen Set of variable gates (IN / MULVAR) already consumed; a second
146 * occurrence means the circuit is not read-once.
147 * @param memo Memoised probability of variable-free (constant-only) gates, so a
148 * shared constant subgraph is evaluated once -- this is what keeps
149 * the whole evaluation @c O(circuit) rather than re-traversing
150 * shared subgraphs. Variable-bearing gates are never memoised (a
151 * re-visit must reach @p seen and throw).
152 * @return Probability at gate @p g.
153 */
155 gate_t g, std::set<gate_t> &seen,
156 std::unordered_map<gate_t, double> &memo) const;
157/**
158 * @brief Iteratively evaluate a certified d-DNNF island.
159 *
160 * Walks the maximal region of certified AND/OR gates (plus the NOT and
161 * input gates they reach) under @p root with an explicit post-order
162 * stack -- certified circuits can be as deep as the data (e.g. the
163 * decomposition-aligned reachability circuits of path-like graphs), so
164 * recursion would overflow the stack. Within the island every gate is
165 * computed once (sharing is licensed by the certificate): certified OR
166 * = sum (determinism), certified AND = product (decomposability), and
167 * each variable registers once in @p seen, so entanglement with the
168 * outside -- or with another island -- throws like a read-once
169 * violation. An uncertified AND/OR inside the island falls back to
170 * @c independentEvaluationInternal() (standard read-once rules).
171 *
172 * @param root Certified gate the island is rooted at.
173 * @param seen Global variable registration shared with the uncertified
174 * region.
175 * @param memo Constant-only global memo (used by the uncertified
176 * fallback).
177 * @return Probability at @p root.
178 */
180 gate_t root, std::set<gate_t> &seen,
181 std::unordered_map<gate_t, double> &memo) const;
182
183public:
184/**
185 * @brief Is gate @p g certified by the d-DNNF per-gate marking?
186 *
187 * @c true iff @p g is an OR marked deterministic or an AND marked
188 * decomposable (info = @c DNNF_CERT_INFO); see @c DNNF_CERT_INFO for the
189 * semantics and trust model.
190 *
191 * @param g Gate to test.
192 * @return Whether the gate carries the d-DNNF certificate.
193 */
194bool isDNNFCertified(gate_t g) const {
195 const auto t = getGateType(g);
196 return (t == BooleanGate::AND || t == BooleanGate::OR) &&
198}
199
200private:
201/**
202 * @brief Recursive helper for @c rewriteMultivaluedGates().
203 * @param muls Gates in the MULVAR group being rewritten.
204 * @param cumulated_probs Cumulative probability thresholds for each MULIN.
205 * @param start First index in @p muls to process.
206 * @param end One past the last index in @p muls to process.
207 * @param prefix Current AND-chain prefix being built.
208 */
210 const std::vector<gate_t> &muls,
211 const std::vector<double> &cumulated_probs,
212 unsigned start,
213 unsigned end,
214 std::vector<gate_t> &prefix);
215
216protected:
217std::set<gate_t> inputs; ///< Set of IN (input) gate IDs
218std::set<gate_t> mulinputs; ///< Set of MULVAR gate IDs
219std::vector<double> prob; ///< Per-gate probability (for IN gates)
220std::map<gate_t, unsigned> info; ///< Per-gate integer info (for MULIN gates)
221bool probabilistic=false; ///< @c true if any gate has a non-unit probability
222
223public:
224/** @brief Construct an empty Boolean circuit. */
228}
229
230/** @copydoc Circuit::addGate() */
231gate_t addGate() override;
232/** @copydoc Circuit::setGate(gateType) */
233gate_t setGate(BooleanGate type) override;
234/** @copydoc Circuit::setGate(const uuid&, gateType) */
235gate_t setGate(const uuid &u, BooleanGate type) override;
236
237/**
238 * @brief Create a new gate with a probability annotation.
239 * @param t Gate type (typically @c BooleanGate::IN).
240 * @param p Probability of this gate being @c true.
241 * @return Gate identifier.
242 */
243gate_t setGate(BooleanGate t, double p);
244
245/**
246 * @brief Create (or update) a gate with a UUID and probability.
247 * @param u UUID string.
248 * @param t Gate type.
249 * @param p Probability of this gate being @c true.
250 * @return Gate identifier.
251 */
252gate_t setGate(const uuid &u, BooleanGate t, double p);
253
254/**
255 * @brief Return the set of input (IN) gate IDs.
256 * @return Const reference to the set of IN gate identifiers.
257 */
258const std::set<gate_t> &getInputs() const {
259 return inputs;
260}
261
262/**
263 * @brief Return @c true if the circuit contains any MULIN gates.
264 *
265 * Multivalued inputs are normally rewritten into AND/OR/NOT/IN gates by
266 * @c rewriteMultivaluedGates() before the circuit is consumed by an
267 * evaluation method. Algorithms that cannot handle multivalued inputs
268 * directly can use this as a precondition check.
269 *
270 * @return @c true iff at least one @c MULIN gate is present.
271 */
273 return !mulinputs.empty();
274}
275
276/**
277 * @brief Set the probability for gate @p g and mark the circuit as probabilistic.
278 * @param g Gate identifier.
279 * @param p Probability value in [0, 1].
280 */
281void setProb(gate_t g, double p) {
282 if(!probabilistic && p!=1.)
283 probabilistic=true;
284 prob[static_cast<std::underlying_type<gate_t>::type>(g)]=p;
285}
286
287/**
288 * @brief Return the probability stored for gate @p g.
289 * @param g Gate identifier.
290 * @return Probability value.
291 */
292double getProb(gate_t g) const {
293 return prob[static_cast<std::underlying_type<gate_t>::type>(g)];
294}
295
296/**
297 * @brief Return @c true if any gate has a non-trivial (< 1) probability.
298 * @return @c true iff at least one gate has a probability strictly less than 1.
299 */
300bool isProbabilistic() const {
301 return probabilistic;
302}
303
304/**
305 * @brief Store an integer annotation on gate @p g.
306 *
307 * Used to record the index of a @c MULIN gate within its @c MULVAR group.
308 *
309 * @param g Gate identifier.
310 * @param info Integer to store.
311 */
312void setInfo(gate_t g, unsigned info);
313
314/**
315 * @brief Return the integer annotation for gate @p g.
316 * @param g Gate identifier.
317 * @return Stored integer, or 0 if not set.
318 */
319unsigned getInfo(gate_t g) const;
320
321/**
322 * @brief Compute the probability by exact enumeration of all possible worlds.
323 *
324 * Only tractable for circuits with a small number of input gates.
325 *
326 * @param g Root gate.
327 * @return Exact probability.
328 */
329double possibleWorlds(gate_t g) const;
330
331/**
332 * @brief Compile the sub-circuit rooted at @p g to a @c dDNNF via an external tool.
333 *
334 * Writes the circuit in DIMACS/DNNF format, invokes @p compiler as a
335 * subprocess, and parses the resulting d-DNNF.
336 *
337 * @param g Root gate.
338 * @param compiler Command to invoke (e.g. "d4", "c2d", "minic2d"). Empty
339 * auto-selects the highest-preference available tool.
340 * @param resolved If non-null, set to the tool actually used (after the
341 * empty -> auto-select resolution), so callers can report
342 * WHICH compiler ran rather than just "compilation".
343 * @return The compiled @c dDNNF.
344 */
345dDNNF compilation(gate_t g, std::string compiler,
346 std::string *resolved = nullptr) const;
347
348/**
349 * @brief Parse a c2d/d4 NNF stream into a @c dDNNF over this circuit's input
350 * gates.
351 *
352 * Shared by the CLI @c compilation() path and the KCMCP client: both obtain
353 * the same NNF text (from a temp file or a socket @c RESULT) and parse it
354 * identically. @p inputOrder maps d4 circuit-mode variables (1..k) to IN
355 * gates; an empty vector means CNF mode, where d-DNNF variable @c v stands
356 * for gate id @c v-1 (real only for IN gates, every other being a Tseytin
357 * auxiliary that is projected out).
358 *
359 * @param in NNF text stream.
360 * @param inputOrder Circuit-mode input-variable to IN-gate map (empty = CNF).
361 * @return The compiled @c dDNNF (empty if the formula is unsat).
362 */
363dDNNF parseDDNNF(std::istream &in,
364 const std::vector<gate_t> &inputOrder) const;
365
366/**
367 * @brief Estimate the probability via Monte Carlo sampling.
368 *
369 * @param g Root gate.
370 * @param samples Number of independent worlds to sample.
371 * @return Estimated probability.
372 */
373double monteCarlo(gate_t g, unsigned samples) const;
374
375/**
376 * @brief Detect the DNF shape the Karp-Luby FPRAS requires.
377 *
378 * Recognises the two tractable regimes: (a) a single AND-of-leaves
379 * clause, or (b) a top-level @c OR whose every child is an AND-only
380 * sub-circuit over @c IN leaves (no @c OR below the root, no @c NOT, no
381 * multivalued input). Cross-clause leaf sharing is allowed and is the
382 * normal Karp-Luby setting.
383 *
384 * On success, @p clauses receives one root per top-level disjunct (or the
385 * singleton root @p g in regime (a)) and @p supports[i] the set of @c IN
386 * leaves reachable from @p clauses[i] through its AND-only stratum -- the
387 * support determines @c Pr[C_i] (the product of leaf marginals) and the
388 * conditional sampler.
389 *
390 * @param g Root gate.
391 * @param clauses Output: the top-level clause roots.
392 * @param supports Output: per-clause set of reachable @c IN leaves.
393 * @return @c true iff the circuit is DNF-shaped (regime (a)/(b)).
394 */
395bool dnfShape(gate_t g,
396 std::vector<gate_t> &clauses,
397 std::vector<std::set<gate_t> > &supports) const;
398
399/**
400 * @brief Cheap shape test: is the circuit DNF-shaped, and how many clauses?
401 *
402 * The @c O(circuit) half of @c dnfShape -- validates the OR-of-ANDs-of-leaves
403 * shape with a single global visited-set (no per-clause re-walk) and returns the
404 * clause count, WITHOUT materialising the per-clause supports (whose total size
405 * can be @c O(m*N)). Used by the chooser to rank @c sieve; the supports are
406 * built only if @c sieve / @c karp-luby actually runs (via @c dnfShape).
407 *
408 * @param g Root gate.
409 * @param num_clauses Output: number of top-level clauses.
410 * @return @c true iff DNF-shaped.
411 */
412bool dnfShapeInfo(gate_t g, std::size_t &num_clauses) const;
413
414/**
415 * @brief Karp-Luby FPRAS estimate of a DNF-shaped circuit's probability
416 * (fixed sample budget, stratified).
417 *
418 * Implements the Karp-Luby coverage estimator for the DNF-counting problem
419 * (@c \#DNF) under tuple-independent inputs: with @c p_i the product of the
420 * marginals of @p supports[i] and @c S the sum of the @c p_i (so @c S in
421 * @c [Pr[F], m*Pr[F]]), the estimator over clause @c i samples a satisfying
422 * assignment of @c C_i (its support forced true, every other leaf drawn from
423 * its marginal), finds the smallest clause index @c j the assignment
424 * satisfies, and accepts iff @c j == i; @c Pr[F] is then @c sum_i p_i times
425 * the per-clause acceptance rate. The acceptance probability is @c Pr[F]/S in
426 * @c [1/m, 1], so the sample count for an @c (eps,delta) guarantee is
427 * independent of @c Pr[F], unlike naive Monte Carlo.
428 *
429 * The @p samples rounds are spread across clauses by *stratified* allocation
430 * (@c n_i proportional to @c p_i/S, every clause sampled at least once),
431 * estimating each clause's acceptance rate separately and combining
432 * @c sum_i p_i * acceptRate_i. This removes the variance of the categorical
433 * clause draw used by the textbook estimator (between-strata variance),
434 * tightening the estimate at the same budget by up to a factor @c m. When
435 * @p samples @c < @c m there are too few rounds for one per clause, so the
436 * method falls back to the unstratified categorical-draw estimator (still
437 * unbiased for any budget).
438 *
439 * The @p clauses / @p supports are those returned by @c dnfShape. The
440 * @c mt19937_64 is seeded from @c provsql.monte_carlo_seed exactly as
441 * @c monteCarlo, so the estimate is reproducible under a pinned seed.
442 *
443 * @param clauses Top-level clause roots (from @c dnfShape).
444 * @param supports Per-clause reachable @c IN leaves (from @c dnfShape).
445 * @param samples Resolved number of sampling rounds.
446 * @return The Karp-Luby probability estimate.
447 */
448double karpLuby(const std::vector<gate_t> &clauses,
449 const std::vector<std::set<gate_t> > &supports,
450 unsigned long samples) const;
451
452/**
453 * @brief Exact probability of a monotone DNF by inclusion-exclusion (sieve).
454 *
455 * @c Pr[∨_i c_i] = Σ_{∅≠S⊆clauses} (-1)^{|S|+1} Pr[∧_{i∈S} c_i]. Each clause is
456 * a conjunction of positive input leaves, so the conjunction of a set @c S of
457 * clauses is the AND of the union of their supports, and over independent
458 * inputs @c Pr[∧_{i∈S} c_i] = ∏_{leaf ∈ ∪supports(S)} getProb(leaf). Exact, and
459 * @c O(2^m) in the clause count @c m -- the portfolio member to pick when @c m
460 * is small (a handful of clauses), where it beats the general compilers.
461 *
462 * @p clauses / @p supports are those returned by @c dnfShape (monotone DNF over
463 * input leaves). Throws when @c m exceeds @c kSieveMaxClauses (the @c 2^m
464 * enumeration would be impractical) so the caller can pick another method.
465 *
466 * @param clauses Top-level clause roots (from @c dnfShape).
467 * @param supports Per-clause reachable @c IN leaves (from @c dnfShape).
468 * @return The exact probability.
469 */
470double sieve(const std::vector<gate_t> &clauses,
471 const std::vector<std::set<gate_t> > &supports) const;
472
473/**
474 * @brief Cheap certified probability interval @c [lower,upper] of a monotone
475 * DNF, without compiling it (Olteanu-Huang-Koch d-tree leaf bound).
476 *
477 * Implements the @c Independent heuristic of Olteanu, Huang & Koch,
478 * "Approximate Confidence Computation in Probabilistic Databases" (ICDE 2010,
479 * Fig. 3). The clauses are greedily partitioned into @e buckets of pairwise
480 * independent clauses (disjoint supports), clauses taken in descending
481 * marginal-probability order so the most probable clauses anchor the buckets.
482 * Each bucket's clauses are mutually independent, so its probability is the
483 * independent-or @c 1-∏(1-P(d)) with @c P(d)=∏_{leaf∈supports[d]} getProb(leaf).
484 * Then, since @c Φ is the disjunction of all buckets:
485 *
486 * - @c lower = max_i P(B_i): a sub-disjunction is a lower bound;
487 * - @c upper = min(1, Σ_i P(B_i)): the union bound.
488 *
489 * Both bounds are sound for @e any partition (the greedy one only affects
490 * tightness), so @c lower ≤ Pr[Φ] ≤ upper always holds. When the clauses are
491 * mutually independent (disjoint supports) they all land in a single bucket and
492 * @c lower=upper=Pr[Φ], i.e. the interval collapses to the exact value.
493 * @c O(m^2) in the clause count @c m.
494 *
495 * A monotone DNF is fully determined (for probability) by its per-clause input
496 * supports, so this takes only the @c supports (the @c set per clause returned
497 * by @c dnfShape, or a cofactor's residual clause set in the @c DTree engine);
498 * the clause root gates are not needed.
499 *
500 * @param clauses Per-clause input-leaf supports (a monotone DNF as a set of
501 * clauses, each a set of @c IN leaves).
502 * @param lower [out] Certified lower bound on @c Pr[Φ].
503 * @param upper [out] Certified upper bound on @c Pr[Φ].
504 */
505void dnfBounds(const std::vector<std::set<gate_t> > &clauses,
506 double &lower, double &upper) const;
507
508/**
509 * @brief Karp-Luby FPRAS with the self-adjusting stopping rule (adaptive
510 * sample count for a relative @c (eps,delta) guarantee).
511 *
512 * The Dagum-Karp-Luby-Ross stopping rule (SICOMP 2000, the optimal form of
513 * the Karp-Luby-Madras 1989 self-adjusting rule): rather than fixing the
514 * number of rounds from the worst-case acceptance probability @c 1/m, draw
515 * coverage trials (clause @c i with probability @c p_i/S, then the
516 * smallest-index coverage test of @c karpLuby) until the *accept count*
517 * reaches the deterministic threshold
518 * @c Y1 = 1 + (1+eps) * 4*(e-2)*ln(2/delta)/eps^2, then return
519 * @c S * Y1 / N over the @c N rounds actually run. That estimate is a
520 * relative @c (eps,delta) approximation of @c Pr[F], and @c N adapts to the
521 * true acceptance probability @c Pr[F]/S (expected @c N is @c Y1*S/Pr[F],
522 * i.e. up to @c m times fewer rounds than the fixed bound when the clauses
523 * barely overlap).
524 *
525 * Sampling stops early at @p max_samples rounds; @p reached_target is then
526 * @c false and the return is the plain unbiased @c S*accepts/N estimate over
527 * the spent budget (the @c (eps,delta) target was not met -- the caller
528 * reports the weaker guarantee actually achieved).
529 *
530 * @param clauses Top-level clause roots (from @c dnfShape).
531 * @param supports Per-clause reachable @c IN leaves (from @c dnfShape).
532 * @param eps Target relative error (in @c (0,1]).
533 * @param delta Target failure probability (in @c (0,1)).
534 * @param max_samples Hard cap on the number of rounds.
535 * @param samples_used Output: rounds actually run.
536 * @param reached_target Output: whether the stopping threshold was reached
537 * before @p max_samples (i.e. the guarantee holds).
538 * @return The Karp-Luby probability estimate.
539 */
540double karpLubyStopping(const std::vector<gate_t> &clauses,
541 const std::vector<std::set<gate_t> > &supports,
542 double eps, double delta,
543 unsigned long max_samples,
544 unsigned long &samples_used,
545 bool &reached_target) const;
546
547/**
548 * @brief Weighted model counting through a registered external counter.
549 *
550 * Generic over the counter: @p tool names a registry record with the @c "wmc"
551 * operation (today @c weightmc, @c ganak, @c sharpsat-td, @c dpmc, or any
552 * tool an administrator registers). The record's @c binary, @c dependencies,
553 * @c argtpl and @c parser drive the whole call -- which weighted-CNF dialect
554 * to write, the command to run, and how to read the count back -- so there is
555 * no per-counter code path. Two output/input conventions are understood, by
556 * @c parser: @c "wmc-line" (MCC-2024 weighted DIMACS in, a @c "c s exact" /
557 * @c "s wmc" count line out) and @c "weightmc" (weightmc's own dialect in, a
558 * @c "mantissa x 2^exp" line out).
559 *
560 * @param g Root gate of the sub-circuit.
561 * @param tool Logical name of the wmc tool to use.
562 * @param opt Tool options; for the approximate counters of the form
563 * @c "delta;epsilon" (drives the @c {pivotAC} placeholder).
564 * @return The weighted model count = P(formula).
565 */
566double wmcCount(gate_t g, const std::string &tool, const std::string &opt) const;
567
568/**
569 * @brief Compute the probability exactly when inputs are independent.
570 *
571 * Applicable when the circuit has no shared input gate (i.e., each
572 * input appears at most once).
573 *
574 * @param g Root gate.
575 * @return Exact probability.
576 */
577double independentEvaluation(gate_t g) const;
578
579/**
580 * @brief Rewrite all MULVAR/MULIN gate clusters into standard AND/OR/NOT circuits.
581 *
582 * Must be called before any evaluation method when the circuit contains
583 * multivalued input gates.
584 */
586
587/**
588 * @brief Build a @c dDNNF directly from the Boolean circuit's structure.
589 *
590 * Used as a fallback when no external compiler is available and the
591 * circuit is already in a form that can be interpreted as a d-DNNF.
592 *
593 * @param g Root gate.
594 * @return A @c dDNNF wrapping the same structure.
595 */
596dDNNF interpretAsDD(gate_t g) const;
597
598/**
599 * @brief Dispatch to the appropriate d-DNNF construction method.
600 *
601 * @param g Root gate.
602 * @param method Compilation method name (e.g. "tree-decomposition",
603 * "d4", "c2d"…).
604 * @param args Additional arguments forwarded to the chosen method.
605 * @return The constructed @c dDNNF.
606 */
607dDNNF makeDD(gate_t g, const std::string &method, const std::string &args) const;
608
609/**
610 * @brief Build a @c dDNNF from a single compiler/route name.
611 *
612 * Resolves the name the way a user (or Studio) thinks of it, with no
613 * separate @c method / @c args split:
614 * - the in-process meta-routes @c "tree-decomposition",
615 * @c "interpret-as-dd", @c "default" (and the empty string) go
616 * through @c makeDD;
617 * - any other name is an external compiler (@c "d4", @c "d4v2",
618 * @c "c2d", @c "minic2d", @c "dsharp", @c "panini-*") and is passed
619 * straight to @c compilation.
620 *
621 * This is the single dispatch point shared by @c compile_to_ddnnf_dot,
622 * @c compile_to_ddnnf (NNF) and @c ddnnf_stats, so they cannot drift on
623 * which names they accept.
624 *
625 * @param g Root gate.
626 * @param name Compiler or meta-route name.
627 * @return The constructed @c dDNNF.
628 */
629dDNNF makeDDByName(gate_t g, const std::string &name) const;
630
631/** @copydoc Circuit::toString() */
632virtual std::string toString(gate_t g) const override;
633
634/**
635 * @brief Render the sub-circuit at @p g, labelling input gates from a map.
636 *
637 * Same as @c toString(gate_t), but @c IN and @c MULIN gates whose
638 * gate identifier is present in @p labels are rendered using the
639 * mapped string instead of the default @c x@<id@> placeholder. Gates
640 * not found in @p labels fall back to the default rendering.
641 *
642 * @param g Gate to render.
643 * @param labels Optional mapping from input/mulinput gate IDs to
644 * user-supplied labels.
645 * @return Human-readable string.
646 */
647std::string toString(
648 gate_t g,
649 const std::unordered_map<gate_t, std::string> &labels) const;
650
651private:
652/**
653 * @brief Internal recursive helper for the two @c toString() variants.
654 *
655 * The @p parent parameter carries the gate type of the immediate
656 * caller. It drives parenthesis elision in two cases: at the root
657 * (parent set to @c UNDETERMINED) the outer wrap is omitted, and
658 * when @p parent matches the current gate type (associative
659 * AND/OR) the wrap is omitted to flatten same-op nesting. A 1-wire
660 * AND/OR also bypasses the wrap and delegates to its child since
661 * such single-element joins carry no information.
662 *
663 * @param g Gate to render.
664 * @param parent Gate type of the caller, or @c UNDETERMINED at the root.
665 * @param labels Pointer to a label map, or @c nullptr for the
666 * unlabelled rendering.
667 */
668std::string toStringHelper(
669 gate_t g,
670 BooleanGate parent,
671 const std::unordered_map<gate_t, std::string> *labels) const;
672
673public:
674
675/**
676 * @brief Export the circuit in the textual format expected by external compilers.
677 *
678 * Produces a multi-line string encoding all gates reachable from the
679 * circuit in the format used by the standalone @c tdkc tool and external
680 * model counters.
681 *
682 * @param g Root gate.
683 * @return Circuit description string.
684 */
685std::string exportCircuit(gate_t g) const;
686
687/**
688 * @brief Return the Tseytin transformation of the sub-circuit at @p g as a DIMACS string.
689 *
690 * Same encoding as the private @c Tseytin file-emitting overload, but
691 * returned in memory without any file I/O. Useful for surfacing the
692 * CNF to a user or to a knowledge-compilation tool over stdin.
693 *
694 * @param g Root gate.
695 * @param display_prob Include @c w lines listing each input's
696 * probability (and its complement).
697 * @param mapping Prepend @c "c input <var> <uuid> <prob>" comment
698 * lines, one per input gate, so the emitted DIMACS
699 * is self-documenting (the comments are ignored by
700 * every model counter / compiler).
701 * @return DIMACS CNF as a string.
702 */
703std::string TseytinCNF(gate_t g, bool display_prob, bool mapping = false) const;
704
705/**
706 * @brief One row of the Tseytin variable mapping.
707 *
708 * Ties a DIMACS variable index back to the provenance input it stands
709 * for, so an external model count / satisfying assignment can be read
710 * against the original circuit.
711 */
713 int variable; ///< DIMACS variable index (= gate id + 1).
714 std::string uuid; ///< Original-circuit UUID (empty if unknown).
715 double probability; ///< Probability assigned to the input gate.
716};
717
718/**
719 * @brief Map each input gate to its DIMACS variable, UUID, probability.
720 *
721 * The variable numbering matches @c TseytinCNF (and @c dDNNF::toNNF):
722 * variable = gate id + 1. Inputs are listed in gate-id order so the
723 * mapping is deterministic.
724 *
725 * @return One @c CNFInputMapping per input gate.
726 */
727std::vector<CNFInputMapping> tseytinVariableMapping() const;
728
729/**
730 * @brief Serialise the sub-circuit at @p g in d4's BC-S1.2 circuit format.
731 *
732 * Emits the Boolean circuit directly (inputs as @c "I" declarations,
733 * AND/OR gates as @c "G name := A|O …" definitions, the root as @c "T"),
734 * inlining @c NOT gates as literal sign flips. This is the input consumed by
735 * d4v2's @c --input-type @c circuit mode, letting us skip the Tseytin
736 * transform.
737 *
738 * Inputs are emitted first, so d4 (which numbers literals from 1 in
739 * first-seen order) assigns them variables 1..k in declaration order;
740 * every variable above k is an internal-gate variable. @p inputOrder is
741 * filled so that d4 variable @c v (1-based) corresponds to input gate
742 * @c inputOrder[v-1], which the d-DNNF parse-back uses to map decision
743 * literals to the right @c IN gate.
744 *
745 * Throws @c CircuitException on a gate shape BC-S1.2 cannot express
746 * (a nullary AND/OR, or a non-AND/OR/NOT/IN gate); the caller falls back
747 * to the Tseytin CNF path.
748 *
749 * @param g Root gate of the sub-circuit.
750 * @param inputOrder Output: input gate for each d4 variable (1-based).
751 * @return BC-S1.2 circuit description as a string.
752 */
753std::string BCS12(gate_t g, std::vector<gate_t> &inputOrder) const;
754
755/**
756 * @brief Parse a Panini (KCBox) DD output file into a ProvSQL d-DNNF.
757 *
758 * The @c panini-dd output parser, selected by @c compilation() for the
759 * @c panini-* records. Those records run the generic compile path (a Tseytin
760 * CNF written to the input, the record's @c argtpl run, the @c --lang carried
761 * in the template); they differ from the @c nnf compilers only in this
762 * parse-back. Panini's DD output is over the variables of our Tseytin CNF:
763 * decisions on input variables are translated to the corresponding @c IN
764 * gates; decisions on Tseytin auxiliaries are dropped (their branches are
765 * mutually exclusive over input assignments by Tseytin determinism, so the
766 * input-projection is still a sound d-DNNF). @c "R2-D2" / @c "CCDD" emit
767 * @c K (kernelize) nodes that break decomposability, so those variants are
768 * not registered and a @c K node here is an error.
769 *
770 * @param outfilename Path to Panini's DD output file.
771 * @return The compiled d-DNNF.
772 */
773dDNNF parsePaniniDD(const std::string &outfilename) const;
774
775/**
776 * @brief Boost serialisation support.
777 * @param ar Boost archive (input or output).
778 * @param version Archive version (unused).
779 */
780template<class Archive>
781void serialize (Archive & ar, const unsigned int version)
782{
783 ar & uuid2id;
784 ar & id2uuid;
785 ar & gates;
786 ar & wires;
787 ar & inputs;
788 ar & mulinputs;
789 ar & prob;
790 ar & info;
791 ar & probabilistic;
792}
793
796};
797
798#endif /* BOOLEAN_CIRCUIT_H */
constexpr unsigned DNNF_CERT_INFO
d-DNNF certificate value for the (gate-type-specific) per-gate info field.
BooleanGate
Gate types for a Boolean provenance circuit.
@ MULVAR
Auxiliary gate grouping all MULIN siblings.
@ NOT
Logical negation of a single child gate.
@ OR
Logical disjunction of child gates.
@ AND
Logical conjunction of child gates.
@ IN
Input (variable) gate representing a base tuple.
@ UNDETERMINED
Placeholder gate whose type has not been set yet.
@ MULIN
Multivalued-input gate (one of several options).
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Out-of-line template method implementations for Circuit<gateType>.
std::vector< double > prob
Per-gate probability (for IN gates).
virtual ~BooleanCircuit()
bool evaluate(gate_t g, const std::unordered_set< gate_t > &sampled) const
Evaluate the sub-circuit at g on one sampled world.
dDNNF interpretAsDD(gate_t g) const
Build a dDNNF directly from the Boolean circuit's structure.
double independentEvaluationInternal(gate_t g, std::set< gate_t > &seen, std::unordered_map< gate_t, double > &memo) const
Recursive helper for independentEvaluation().
double sieve(const std::vector< gate_t > &clauses, const std::vector< std::set< gate_t > > &supports) const
Exact probability of a monotone DNF by inclusion-exclusion (sieve).
double possibleWorlds(gate_t g) const
Compute the probability by exact enumeration of all possible worlds.
std::vector< CNFInputMapping > tseytinVariableMapping() const
Map each input gate to its DIMACS variable, UUID, probability.
double karpLubyStopping(const std::vector< gate_t > &clauses, const std::vector< std::set< gate_t > > &supports, double eps, double delta, unsigned long max_samples, unsigned long &samples_used, bool &reached_target) const
Karp-Luby FPRAS with the self-adjusting stopping rule (adaptive sample count for a relative (eps,...
bool probabilistic
true if any gate has a non-unit probability
void setProb(gate_t g, double p)
Set the probability for gate g and mark the circuit as probabilistic.
dDNNF parseDDNNF(std::istream &in, const std::vector< gate_t > &inputOrder) const
Parse a c2d/d4 NNF stream into a dDNNF over this circuit's input gates.
std::set< gate_t > inputs
Set of IN (input) gate IDs.
bool isDNNFCertified(gate_t g) const
Is gate g certified by the d-DNNF per-gate marking?
void rewriteMultivaluedGatesRec(const std::vector< gate_t > &muls, const std::vector< double > &cumulated_probs, unsigned start, unsigned end, std::vector< gate_t > &prefix)
Recursive helper for rewriteMultivaluedGates().
double evaluateCertifiedIsland(gate_t root, std::set< gate_t > &seen, std::unordered_map< gate_t, double > &memo) const
Iteratively evaluate a certified d-DNNF island.
std::string exportCircuit(gate_t g) const
Export the circuit in the textual format expected by external compilers.
const std::set< gate_t > & getInputs() const
Return the set of input (IN) gate IDs.
std::string BCS12(gate_t g, std::vector< gate_t > &inputOrder) const
Serialise the sub-circuit at g in d4's BC-S1.2 circuit format.
std::string TseytinCNF(gate_t g, bool display_prob, bool mapping=false) const
Return the Tseytin transformation of the sub-circuit at g as a DIMACS string.
dDNNF parsePaniniDD(const std::string &outfilename) const
Parse a Panini (KCBox) DD output file into a ProvSQL d-DNNF.
std::string toStringHelper(gate_t g, BooleanGate parent, const std::unordered_map< gate_t, std::string > *labels) const
Internal recursive helper for the two toString() variants.
double karpLuby(const std::vector< gate_t > &clauses, const std::vector< std::set< gate_t > > &supports, unsigned long samples) const
Karp-Luby FPRAS estimate of a DNF-shaped circuit's probability (fixed sample budget,...
dDNNF compilation(gate_t g, std::string compiler, std::string *resolved=nullptr) const
Compile the sub-circuit rooted at g to a dDNNF via an external tool.
dDNNF makeDD(gate_t g, const std::string &method, const std::string &args) const
Dispatch to the appropriate d-DNNF construction method.
gate_t setGate(BooleanGate type) override
Allocate a new gate with type type and no UUID.
unsigned getInfo(gate_t g) const
Return the integer annotation for gate g.
bool hasMultivaluedGates() const
Return true if the circuit contains any MULIN gates.
friend class dDNNFTreeDecompositionBuilder
double wmcCount(gate_t g, const std::string &tool, const std::string &opt) const
Weighted model counting through a registered external counter.
gate_t addGate() override
Allocate a new gate with a default-initialised type.
double monteCarlo(gate_t g, unsigned samples) const
Estimate the probability via Monte Carlo sampling.
void rewriteMultivaluedGates()
Rewrite all MULVAR/MULIN gate clusters into standard AND/OR/NOT circuits.
double getProb(gate_t g) const
Return the probability stored for gate g.
bool dnfShapeInfo(gate_t g, std::size_t &num_clauses) const
Cheap shape test: is the circuit DNF-shaped, and how many clauses?
void setInfo(gate_t g, unsigned info)
Store an integer annotation on gate g.
void dnfBounds(const std::vector< std::set< gate_t > > &clauses, double &lower, double &upper) const
Cheap certified probability interval [lower,upper] of a monotone DNF, without compiling it (Olteanu-H...
virtual std::string toString(gate_t g) const override
Return a textual description of gate g for debugging.
bool dnfShape(gate_t g, std::vector< gate_t > &clauses, std::vector< std::set< gate_t > > &supports) const
Detect the DNF shape the Karp-Luby FPRAS requires.
BooleanCircuit()
Construct an empty Boolean circuit.
void serialize(Archive &ar, const unsigned int version)
Boost serialisation support.
std::map< gate_t, unsigned > info
Per-gate integer info (for MULIN gates).
friend class boost::serialization::access
gate_t interpretCertifiedIsland(gate_t root, std::set< gate_t > &seen, dDNNF &dd) const
Iteratively copy a certified island into dd.
dDNNF makeDDByName(gate_t g, const std::string &name) const
Build a dDNNF from a single compiler/route name.
bool isProbabilistic() const
Return true if any gate has a non-trivial (< 1) probability.
gate_t interpretAsDDInternal(gate_t g, std::set< gate_t > &seen, dDNNF &dd) const
Recursive helper for interpretAsDD().
double independentEvaluation(gate_t g) const
Compute the probability exactly when inputs are independent.
std::set< gate_t > mulinputs
Set of MULVAR gate IDs.
Generic template base class for provenance circuits.
Definition Circuit.h:62
std::string uuid
Definition Circuit.h:65
BooleanGate getGateType(gate_t g) const
Definition Circuit.h:130
std::unordered_map< gate_t, uuid > id2uuid
Definition Circuit.h:69
std::unordered_map< uuid, gate_t > uuid2id
Definition Circuit.h:68
std::vector< BooleanGate > gates
Definition Circuit.h:71
std::vector< std::vector< gate_t > > wires
Definition Circuit.h:72
A d-DNNF circuit supporting exact probabilistic and game-theoretic evaluation.
Definition dDNNF.h:71
One row of the Tseytin variable mapping.
int variable
DIMACS variable index (= gate id + 1).
double probability
Probability assigned to the input gate.
std::string uuid
Original-circuit UUID (empty if unknown).