ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
BooleanCircuit.cpp
Go to the documentation of this file.
1/**
2 * @file BooleanCircuit.cpp
3 * @brief Boolean circuit implementation and evaluation algorithms.
4 *
5 * Implements the methods declared in @c BooleanCircuit.h, including:
6 * - Gate management (@c addGate, @c setGate, @c setInfo, @c setProb).
7 * - Probability evaluation algorithms: possible worlds, Monte Carlo,
8 * WeightMC, independent evaluation.
9 * - Knowledge compilation: @c compilation() (external tools),
10 * @c interpretAsDD() (direct from circuit structure),
11 * @c makeDD() (dispatcher).
12 * - @c rewriteMultivaluedGates(): replaces MULVAR/MULIN clusters with
13 * standard AND/OR/NOT circuits.
14 * - @c Tseytin(): DIMACS/weighted CNF generation for model counters.
15 * - @c exportCircuit(): serialisation in the @c tdkc text format.
16 * - @c toString(): human-readable gate description.
17 *
18 * In the standalone @c tdkc build (when @c TDKC is defined) a lightweight
19 * @c elog() stub replaces the PostgreSQL error-reporting function.
20 */
21#include "BooleanCircuit.h"
22#include "Circuit.hpp"
23#include <type_traits>
24
25extern "C" {
26#include <unistd.h>
27#include <sys/wait.h>
28#include <math.h>
29}
30
31#include <cassert>
32#include <cstdint>
33#include <string>
34#include <fstream>
35#include <sstream>
36#include <cstdlib>
37#include <iostream>
38#include <random>
39#include <vector>
40#include <stack>
41
42#include <boost/archive/text_oarchive.hpp>
43#include <boost/archive/text_iarchive.hpp>
44
46#include "external_tool.h"
47
48// "provsql_utils.h"
49#ifdef TDKC
50constexpr bool provsql_interrupted = false;
51constexpr int provsql_verbose = 0;
52constexpr int provsql_monte_carlo_seed = -1;
53enum levels {ERROR, NOTICE};
54#define elog(level, ...) {fprintf(stderr, __VA_ARGS__); if(level==ERROR) exit(EXIT_FAILURE);}
55#define CHECK_FOR_INTERRUPTS() ((void)0)
56#else
57extern "C" {
58#include "provsql_utils.h"
59#include "utils/elog.h"
60#include "miscadmin.h"
61}
62#endif
63#include "provsql_error.h"
64
66{
67 auto id = Circuit::setGate(type);
68 if(type == BooleanGate::IN) {
69 setProb(id,1.);
70 inputs.insert(id);
71 } else if(type == BooleanGate::MULIN) {
72 mulinputs.insert(id);
73 }
74 return id;
75}
76
78{
79 auto id = Circuit::setGate(u, type);
80 if(type == BooleanGate::IN) {
81 setProb(id,1.);
82 inputs.insert(id);
83 } else if(type == BooleanGate::MULIN) {
84 mulinputs.insert(id);
85 }
86 return id;
87}
88
90{
91 auto id = setGate(u, type);
92 if(std::isnan(p))
93 p=1.;
94 setProb(id,p);
95 return id;
96}
97
99{
100 auto id = setGate(type);
101 if(std::isnan(p))
102 p=1.;
103 setProb(id,p);
104 return id;
105}
106
108{
109 auto id=Circuit::addGate();
110 prob.push_back(1);
111 return id;
112}
113
115{
116 return toStringHelper(g, BooleanGate::UNDETERMINED, nullptr);
117}
118
120 gate_t g,
121 const std::unordered_map<gate_t, std::string> &labels) const
122{
123 return toStringHelper(g, BooleanGate::UNDETERMINED, &labels);
124}
125
127 gate_t g,
128 BooleanGate parent,
129 const std::unordered_map<gate_t, std::string> *labels) const
130{
131 std::string op;
132 std::string result;
133 auto gtype = getGateType(g);
134
135 switch(gtype) {
136 case BooleanGate::IN:
137 if(labels) {
138 auto it = labels->find(g);
139 if(it != labels->end())
140 return it->second;
141 }
142 return "x"+to_string(g);
144 if(labels) {
145 auto it = labels->find(g);
146 if(it != labels->end())
147 return it->second + "[" + std::to_string(getProb(g)) + "]";
148 }
149 return "{" + to_string(*getWires(g).begin()) + "=" + std::to_string(getInfo(g)) + "}[" + std::to_string(getProb(g)) + "]";
150 case BooleanGate::NOT:
151 op="¬";
152 break;
154 op="?";
155 break;
156 case BooleanGate::AND:
157 op="∧";
158 break;
159 case BooleanGate::OR:
160 op="∨";
161 break;
163 ; // already dealt with in MULIN
164 }
165
166 if(getWires(g).empty()) {
167 if(gtype==BooleanGate::AND)
168 return "⊤";
169 else if(gtype==BooleanGate::OR)
170 return "⊥";
171 else return op;
172 }
173
174 for(auto s: getWires(g)) {
175 if(gtype==BooleanGate::NOT)
176 result = op;
177 else if(!result.empty())
178 result+=" "+op+" ";
179 result+=toStringHelper(s, gtype, labels);
180 }
181
182 // Parenthesis elision:
183 // * single-wire AND/OR: the join carries no information, drop the wrap.
184 // * root call (parent = UNDETERMINED): no enclosing context, drop the wrap.
185 // * same-op nesting (parent == gtype, AND/OR only): associative, drop the wrap.
186 bool single_join = (gtype==BooleanGate::AND || gtype==BooleanGate::OR)
187 && getWires(g).size()==1;
188 bool same_op_assoc = (gtype==BooleanGate::AND || gtype==BooleanGate::OR)
189 && parent==gtype;
190 if(single_join || parent==BooleanGate::UNDETERMINED || same_op_assoc)
191 return result;
192 return "("+result+")";
193}
194
196{
197 std::stringstream ss;
198
199 std::unordered_set<gate_t> processed;
200 std::stack<gate_t> to_process;
201 to_process.push(root);
202
203 while(!to_process.empty()) {
204 auto g=to_process.top();
205 to_process.pop();
206
207 if(processed.find(g)!=processed.end())
208 continue;
209
210 ss << g << " ";
211
212 switch(getGateType(g)) {
213 case BooleanGate::IN:
214 ss << "IN " << getProb(g);
215 break;
216
217 case BooleanGate::NOT:
218 ss << "NOT " << getWires(g)[0];
219 break;
220
221 case BooleanGate::AND:
222 ss << "AND";
223
224 for(auto s:getWires(g))
225 ss << " " << s;
226 break;
227
228 case BooleanGate::OR:
229 ss << "OR";
230
231 for(auto s:getWires(g))
232 ss << " " << s;
233 break;
234
238 assert(false); // not done
239 }
240
241 ss << "\n";
242
243 for(auto s: getWires(g)) {
244 if(processed.find(s)==processed.end())
245 to_process.push(s);
246 }
247
248 processed.insert(g);
249 }
250
251 return ss.str();
252}
253
254bool BooleanCircuit::evaluate(gate_t g, const std::unordered_set<gate_t> &sampled) const
255{
256 bool disjunction=false;
257
258 switch(getGateType(g)) {
259 case BooleanGate::IN:
260 return sampled.find(g)!=sampled.end();
263 throw CircuitException("Monte-Carlo sampling not implemented on multivalued inputs");
264 case BooleanGate::NOT:
265 return !evaluate(*(getWires(g).begin()), sampled);
266 case BooleanGate::AND:
267 disjunction = false;
268 break;
269 case BooleanGate::OR:
270 disjunction = true;
271 break;
273 throw CircuitException("Incorrect gate type");
274 }
275
276 for(auto s: getWires(g)) {
277 bool e = evaluate(s, sampled);
278 if(disjunction && e)
279 return true;
280 if(!disjunction && !e)
281 return false;
282 }
283
284 if(disjunction)
285 return false;
286 else
287 return true;
288}
289
290double BooleanCircuit::monteCarlo(gate_t g, unsigned samples) const
291{
292 // Seed mt19937_64 from the provsql.monte_carlo_seed GUC: -1 (the
293 // default) means non-deterministic via std::random_device, any other
294 // value (including 0) is a literal seed so regression tests can pin
295 // sampling for reproducibility.
296 std::mt19937_64 rng;
297 if(provsql_monte_carlo_seed != -1) {
298 rng.seed(static_cast<uint64_t>(provsql_monte_carlo_seed));
299 } else {
300 std::random_device rd;
301 rng.seed((static_cast<uint64_t>(rd()) << 32) | rd());
302 }
303 std::uniform_real_distribution<double> uniform01(0.0, 1.0);
304
305 auto success{0u};
306
307 for(unsigned i=0; i<samples; ++i) {
308 std::unordered_set<gate_t> sampled;
309 for(auto in: inputs) {
310 if(uniform01(rng) < getProb(in)) {
311 sampled.insert(in);
312 }
313 }
314
315 if(evaluate(g, sampled))
316 ++success;
317
319 throw CircuitException("Interrupted after "+std::to_string(i+1)+" samples");
320 }
321
322 return success*1./samples;
323}
324
326{
327 if(inputs.size()>=8*sizeof(unsigned long long))
328 throw CircuitException("Too many possible worlds to iterate over");
329
330 unsigned long long nb=(1<<inputs.size());
331 double totalp=0.;
332
333 for(unsigned long long i=0; i < nb; ++i) {
334 std::unordered_set<gate_t> s;
335 double p = 1;
336
337 unsigned j=0;
338 for(gate_t in : inputs) {
339 if(i & (1 << j)) {
340 s.insert(in);
341 p*=getProb(in);
342 } else {
343 p*=1-getProb(in);
344 }
345 ++j;
346 }
347
348 if(evaluate(g, s))
349 totalp+=p;
350
352 throw CircuitException("Interrupted");
353 }
354
355 return totalp;
356}
357
358std::string BooleanCircuit::Tseytin(gate_t g, bool display_prob=false) const {
359 std::vector<std::vector<int> > clauses;
360
361 // Tseytin transformation
362 for(gate_t i{0}; i<gates.size(); ++i) {
363 switch(getGateType(i)) {
364 case BooleanGate::AND:
365 {
366 int id{static_cast<int>(i)+1};
367 std::vector<int> c = {id};
368 for(auto s: getWires(i)) {
369 clauses.push_back({-id, static_cast<int>(s)+1});
370 c.push_back(-static_cast<int>(s)-1);
371 }
372 clauses.push_back(c);
373 break;
374 }
375
376 case BooleanGate::OR:
377 {
378 int id{static_cast<int>(i)+1};
379 std::vector<int> c = {-id};
380 for(auto s: getWires(i)) {
381 clauses.push_back({id, -static_cast<int>(s)-1});
382 c.push_back(static_cast<int>(s)+1);
383 }
384 clauses.push_back(c);
385 }
386 break;
387
388 case BooleanGate::NOT:
389 {
390 int id=static_cast<int>(i)+1;
391 auto s=*getWires(i).begin();
392 clauses.push_back({-id,-static_cast<int>(s)-1});
393 clauses.push_back({id,static_cast<int>(s)+1});
394 break;
395 }
396
398 throw CircuitException("Multivalued inputs should have been removed by then.");
400 case BooleanGate::IN:
402 ;
403 }
404 }
405 clauses.push_back({(int)g+1});
406
407 // Use a private 0700 directory rather than a bare mkstemp file so the
408 // sibling output paths (.nnf / .out, derived deterministically from
409 // this base) cannot be raced by a local user pre-creating a symlink
410 // before the external tool opens them.
411 char cdir[] = "/tmp/provsqlXXXXXX";
412 if(mkdtemp(cdir) == NULL) {
413 throw CircuitException("Cannot create temporary directory");
414 }
415 std::string filename=std::string(cdir)+"/input";
416 std::ofstream ofs(filename.c_str());
417
418 ofs << "p cnf " << gates.size() << " " << clauses.size() << "\n";
419
420 for(unsigned i=0; i<clauses.size(); ++i) {
421 for(int x : clauses[i]) {
422 ofs << x << " ";
423 }
424 ofs << "0\n";
425 }
426 if(display_prob) {
427 for(gate_t in: inputs) {
428 ofs << "w " << (static_cast<std::underlying_type<gate_t>::type>(in)+1) << " " << getProb(in) << "\n";
429 ofs << "w -" << (static_cast<std::underlying_type<gate_t>::type>(in)+1) << " " << (1. - getProb(in)) << "\n";
430 }
431 }
432
433 ofs.close();
434
435 return filename;
436}
437
438dDNNF BooleanCircuit::compilation(gate_t g, std::string compiler) const {
439 std::string filename=BooleanCircuit::Tseytin(g);
440 std::string outfilename=filename+".nnf";
441
442 if(provsql_verbose>=20) {
443 provsql_notice("Tseytin circuit in %s", filename.c_str());
444 }
445
446 bool new_d4 {false};
447 std::string cmdline=compiler+" ";
448 if(compiler=="d4") {
449 cmdline+="-dDNNF "+filename+" -out="+outfilename;
450 new_d4 = true;
451 } else if(compiler=="c2d") {
452 cmdline+="-in "+filename+" -silent";
453 } else if(compiler=="minic2d") {
454 cmdline+="-in "+filename;
455 } else if(compiler=="dsharp") {
456 cmdline+="-q -Fnnf "+outfilename+" "+filename;
457 } else {
458 throw CircuitException("Unknown compiler '"+compiler+"'");
459 }
460
461 if(find_external_tool(compiler).empty())
462 throw CircuitException(
463 compiler + " not found on PATH; install it or add its "
464 "directory to provsql.tool_search_path");
465
466 int retvalue=run_external_tool(cmdline);
467
468 // PG's StatementTimeoutHandler (and pg_cancel_backend, etc.) sends
469 // SIGINT to the whole process group via kill(-MyProcPid, SIGINT).
470 // The child compiler in our group dies on default SIGINT, but
471 // glibc system() temporarily SIG_IGNs SIGINT in the parent for the
472 // duration of the wait, so the same signal is silently discarded
473 // here and InterruptPending / QueryCancelPending are never set.
474 // Translate that wstatus into a proper PG cancel so the
475 // CHECK_FOR_INTERRUPTS below raises 57014 instead of us either
476 // throwing "Error executing", or falling through to the legacy
477 // d4-syntax retry on the corpse (which then mis-parses an empty
478 // .nnf as "Unreadable d-DNNF" XX000).
479#ifndef TDKC
480 if(WIFSIGNALED(retvalue) && WTERMSIG(retvalue) == SIGINT) {
481 InterruptPending = true;
482 QueryCancelPending = true;
483 }
484#endif
485
486 CHECK_FOR_INTERRUPTS();
487
488 // PG's StatementTimeoutHandler (and pg_cancel_backend, etc.) sends
489 // SIGINT to the whole process group via kill(-MyProcPid, SIGINT).
490 // The child compiler in our group dies on default SIGINT, but
491 // glibc system() temporarily SIG_IGNs SIGINT in the parent for the
492 // duration of the wait, so the same signal is silently discarded
493 // here and InterruptPending / QueryCancelPending are never set.
494 // Translate that wstatus into a proper PG cancel so the
495 // CHECK_FOR_INTERRUPTS below raises 57014 instead of us either
496 // throwing "Error executing", or falling through to the legacy
497 // d4-syntax retry on the corpse (which then mis-parses an empty
498 // .nnf as "Unreadable d-DNNF" XX000).
499#ifndef TDKC
500 if(WIFSIGNALED(retvalue) && WTERMSIG(retvalue) == SIGINT) {
501 InterruptPending = true;
502 QueryCancelPending = true;
503 }
504#endif
505
506 CHECK_FOR_INTERRUPTS();
507
508 if(retvalue && compiler=="d4") {
509 // Temporary support for older version of d4
510 new_d4 = false;
511 cmdline = "d4 "+filename+" -out="+outfilename;
512 retvalue=run_external_tool(cmdline);
513#ifndef TDKC
514 if(WIFSIGNALED(retvalue) && WTERMSIG(retvalue) == SIGINT) {
515 InterruptPending = true;
516 QueryCancelPending = true;
517 }
518#endif
519 }
520
521 CHECK_FOR_INTERRUPTS();
522
523 if(retvalue)
524 throw CircuitException(format_external_tool_status(retvalue, compiler));
525
526 if(provsql_verbose<20) {
527 if(unlink(filename.c_str())) {
528 throw CircuitException("Error removing "+filename);
529 }
530 }
531
532 std::ifstream ifs(outfilename.c_str());
533
534 std::string line;
535 getline(ifs,line);
536
537 if(line.rfind("nnf", 0) != 0) {
538 // New d4 does not include this magic line
539
540 if(compiler != "d4") {
541 // unsatisfiable formula
542 return dDNNF();
543 }
544 } else {
545 std::string nnf;
546 unsigned nb_nodes, nb_edges, nb_variables;
547
548 std::stringstream ss(line);
549 ss >> nnf >> nb_nodes >> nb_edges >> nb_variables;
550
551 if(nb_variables!=gates.size())
552 throw CircuitException("Unreadable d-DNNF (wrong number of variables: " + std::to_string(nb_variables) +" vs " + std::to_string(gates.size()) + ")");
553
554 getline(ifs,line);
555 }
556
557 dDNNF dnnf;
558
559 unsigned i=0;
560 do {
561 std::stringstream ss(line);
562
563 std::string c;
564 ss >> c;
565
566 if(c=="O") {
567 int var, args;
568 ss >> var >> args;
569 auto id=dnnf.getGate(std::to_string(i));
570 dnnf.setGate(std::to_string(i), BooleanGate::OR);
571 int g;
572 while(ss >> g) {
573 auto id2=dnnf.getGate(std::to_string(g));
574 dnnf.addWire(id,id2);
575 }
576 } else if(c=="A") {
577 int args;
578 ss >> args;
579 auto id=dnnf.getGate(std::to_string(i));
580 dnnf.setGate(std::to_string(i), BooleanGate::AND);
581 int g;
582 while(ss >> g) {
583 auto id2=dnnf.getGate(std::to_string(g));
584 dnnf.addWire(id,id2);
585 }
586 } else if(c=="L") {
587 int leaf;
588 ss >> leaf;
589 auto and_gate=dnnf.setGate(std::to_string(i), BooleanGate::AND);
590 if(gates[abs(leaf)-1]==BooleanGate::IN) {
591 if(leaf<0) {
592 auto leaf_gate = dnnf.setGate(getUUID(static_cast<gate_t>(-leaf-1)), BooleanGate::IN, prob[-leaf-1]);
593 auto not_gate = dnnf.setGate(BooleanGate::NOT);
594 dnnf.addWire(not_gate, leaf_gate);
595 dnnf.addWire(and_gate, not_gate);
596 } else {
597 auto leaf_gate = dnnf.setGate(getUUID(static_cast<gate_t>(leaf-1)), BooleanGate::IN, prob[leaf-1]);
598 dnnf.addWire(and_gate, leaf_gate);
599 }
600 } else {
601 ; // Do nothing, TRUE gate
602 }
603 } else if(c=="f" || c=="o") {
604 // d4 extended format
605 // A FALSE gate is an OR gate without wires
606 int var;
607 ss >> var;
608 dnnf.setGate(std::to_string(var), BooleanGate::OR);
609 } else if(c=="t" || c=="a") {
610 // d4 extended format
611 // A TRUE gate is an AND gate without wires
612 int var;
613 ss >> var;
614 dnnf.setGate(std::to_string(var), BooleanGate::AND);
615 } else if(dnnf.hasGate(c)) {
616 // d4 extended format
617 int var;
618 ss >> var;
619 auto id2=dnnf.getGate(std::to_string(var));
620
621 std::vector<int> decisions;
622 int decision;
623 while(ss >> decision) {
624 if(decision==0)
625 break;
626 if(gates[abs(decision)-1]==BooleanGate::IN)
627 decisions.push_back(decision);
628 }
629
630 if(decisions.empty()) {
631 dnnf.addWire(dnnf.getGate(c), id2);
632 } else {
633 auto and_gate = dnnf.setGate(BooleanGate::AND);
634 dnnf.addWire(dnnf.getGate(c), and_gate);
635 dnnf.addWire(and_gate, id2);
636 for(auto leaf : decisions) {
637 if(leaf<0) {
638 auto leaf_gate = dnnf.setGate(getUUID(static_cast<gate_t>(-leaf-1)), BooleanGate::IN, prob[-leaf-1]);
639 auto not_gate = dnnf.setGate(BooleanGate::NOT);
640 dnnf.addWire(not_gate, leaf_gate);
641 dnnf.addWire(and_gate, not_gate);
642 } else {
643 auto leaf_gate = dnnf.setGate(getUUID(static_cast<gate_t>(leaf-1)), BooleanGate::IN, prob[leaf-1]);
644 dnnf.addWire(and_gate, leaf_gate);
645 }
646 }
647 }
648 } else
649 throw CircuitException(std::string("Unreadable d-DNNF (unknown node type: ")+c+")");
650
651 ++i;
652 } while(getline(ifs, line));
653
654 ifs.close();
655
656 if(provsql_verbose<20) {
657 if(unlink(outfilename.c_str())) {
658 throw CircuitException("Error removing "+outfilename);
659 }
660 std::string dirname=filename.substr(0, filename.rfind('/'));
661 if(rmdir(dirname.c_str())) {
662 throw CircuitException("Error removing temp directory "+dirname);
663 }
664 } else
665 provsql_notice("Compiled d-DNNF in %s", outfilename.c_str());
666
667 dnnf.setRoot(dnnf.getGate(new_d4?"1":std::to_string(i-1)));
668
669 return dnnf;
670}
671
672double BooleanCircuit::WeightMC(gate_t g, std::string opt) const {
673 std::string filename=BooleanCircuit::Tseytin(g, true);
674
675 //opt of the form 'delta;epsilon'
676 std::stringstream ssopt(opt);
677 std::string delta_s, epsilon_s;
678 getline(ssopt, delta_s, ';');
679 getline(ssopt, epsilon_s, ';');
680
681 double delta = 0;
682 try {
683 delta=stod(delta_s);
684 } catch (std::invalid_argument &) {
685 delta=0;
686 }
687 double epsilon = 0;
688 try {
689 epsilon=stod(epsilon_s);
690 } catch (std::invalid_argument &) {
691 epsilon=0;
692 }
693 if(delta == 0) delta=0.2;
694 if(epsilon == 0) epsilon=0.8;
695
696 //TODO calcul numIterations
697
698 //calcul pivotAC
699 const double pivotAC=2*ceil(exp(3./2)*(1+1/epsilon)*(1+1/epsilon));
700
701 if(find_external_tool("weightmc").empty())
702 throw CircuitException(
703 "weightmc not found on PATH; install it or add its "
704 "directory to provsql.tool_search_path");
705
706 std::string cmdline="weightmc --startIteration=0 --gaussuntil=400 --verbosity=0 --pivotAC="+std::to_string(pivotAC)+ " "+filename+" > "+filename+".out";
707
708 int retvalue=run_external_tool(cmdline);
709 if(retvalue) {
710 throw CircuitException(format_external_tool_status(retvalue, "weightmc"));
711 }
712
713 //parsing
714 std::ifstream ifs((filename+".out").c_str());
715 std::string line, prev_line;
716 while(getline(ifs,line))
717 prev_line=line;
718
719 std::stringstream ss(prev_line);
720 std::string result;
721 ss >> result >> result >> result >> result >> result;
722
723 std::istringstream iss(result);
724 std::string val, exp;
725 getline(iss, val, 'x');
726 getline(iss, exp);
727 double value=stod(val);
728 exp=exp.substr(2);
729 double exponent=stod(exp);
730 double ret=value*(pow(2.0,exponent));
731
732 if(unlink(filename.c_str())) {
733 throw CircuitException("Error removing "+filename);
734 }
735
736 if(unlink((filename+".out").c_str())) {
737 throw CircuitException("Error removing "+filename+".out");
738 }
739
740 std::string dirname=filename.substr(0, filename.rfind('/'));
741 if(rmdir(dirname.c_str())) {
742 throw CircuitException("Error removing temp directory "+dirname);
743 }
744
745 return ret;
746}
747
749 gate_t g, std::set<gate_t> &seen) const
750{
751 double result=1.;
752
753 switch(getGateType(g)) {
754 case BooleanGate::AND:
755 for(const auto &c: getWires(g)) {
756 result*=independentEvaluationInternal(c, seen);
757 }
758 break;
759
760 case BooleanGate::OR:
761 {
762 // We collect probability among each group of children, where we
763 // group MULIN gates with the same key var together
764 std::map<gate_t, double> groups;
765 std::set<gate_t> local_mulins;
766 std::set<std::pair<gate_t, unsigned> > mulin_seen;
767
768 for(const auto &c: getWires(g)) {
769 auto group = c;
771 group = *getWires(c).begin();
772 if(local_mulins.find(group)==local_mulins.end()) {
773 if(seen.find(group)!=seen.end())
774 throw CircuitException("Not an independent circuit");
775 else
776 seen.insert(group);
777 local_mulins.insert(group);
778 }
779 auto p = std::make_pair(group, getInfo(c));
780 if(mulin_seen.find(p)==mulin_seen.end()) {
781 groups[group] += getProb(c);
782 mulin_seen.insert(p);
783 }
784 } else
785 groups[group] = independentEvaluationInternal(c, seen);
786 }
787
788 for(const auto [k, v]: groups)
789 result *= 1-v;
790 result = 1-result;
791 }
792 break;
793
794 case BooleanGate::NOT:
795 result=1-independentEvaluationInternal(*getWires(g).begin(), seen);
796 break;
797
798 case BooleanGate::IN:
799 {
800 /* A leaf with probability 0 or 1 is a constant : it carries no
801 * Boolean variable that can collide with another occurrence of
802 * itself. Skip the seen-set bookkeeping so circuits where the
803 * shared subgraphs are all constants (e.g. RangeCheck-resolved
804 * comparators flowing through a non-tree structure, or
805 * user-flipped Bernoullis pinned to 0 / 1) stay evaluable under
806 * the read-once `independent` method. Anything strictly between
807 * 0 and 1 is a real Bernoulli variable and must remain
808 * read-once. */
809 const double p = getProb(g);
810 if (p == 0.0 || p == 1.0) {
811 result = p;
812 break;
813 }
814 if(seen.find(g)!=seen.end())
815 throw CircuitException("Not an independent circuit");
816 seen.insert(g);
817 result=p;
818 }
819 break;
820
822 {
823 auto child = *getWires(g).begin();
824 if(seen.find(child)!=seen.end())
825 throw CircuitException("Not an independent circuit");
826 seen.insert(child);
827 result=getProb(g);
828 }
829 break;
830
833 throw CircuitException("Bad gate");
834 }
835
836 return result;
837}
838
840{
841 std::set<gate_t> seen;
842 return independentEvaluationInternal(g, seen);
843}
844
845void BooleanCircuit::setInfo(gate_t g, unsigned int i)
846{
847 info[g] = i;
848}
849
851{
852 auto it = info.find(g);
853
854 if(it==info.end())
855 return 0;
856 else
857 return it->second;
858}
859
861 const std::vector<gate_t> &muls,
862 const std::vector<double> &cumulated_probs,
863 unsigned start,
864 unsigned end,
865 std::vector<gate_t> &prefix)
866{
867 if(start==end) {
868 getWires(muls[start]) = prefix;
869 return;
870 }
871
872 unsigned mid = (start+end)/2;
873 // cumulated_probs is an *inclusive* prefix sum (cumulated_probs[i] =
874 // p[0]+...+p[i]). The conditional probability of being in the left
875 // half [start..mid] given the range [start..end] is therefore
876 // (cum[mid] - cum[start-1]) / (cum[end] - cum[start-1])
877 // with cum[-1] treated as 0 when start==0.
878 double prev_start = (start == 0) ? 0. : cumulated_probs[start - 1];
879 auto g = setGate(
881 (cumulated_probs[mid] - prev_start) /
882 (cumulated_probs[end] - prev_start));
883 auto not_g = setGate(BooleanGate::NOT);
884 getWires(not_g).push_back(g);
885
886 prefix.push_back(g);
887 rewriteMultivaluedGatesRec(muls, cumulated_probs, start, mid, prefix);
888 prefix.pop_back();
889 prefix.push_back(not_g);
890 rewriteMultivaluedGatesRec(muls, cumulated_probs, mid+1, end, prefix);
891 prefix.pop_back();
892}
893
894/**
895 * @brief Check whether two double values are approximately equal.
896 * @param a First value.
897 * @param b Second value.
898 * @return @c true if @p a and @p b differ by less than 10× machine epsilon.
899 */
900static constexpr bool almost_equals(double a, double b)
901{
902 double diff = a - b;
903 constexpr double epsilon = std::numeric_limits<double>::epsilon() * 10;
904
905 return (diff < epsilon && diff > -epsilon);
906}
907
909{
910 std::map<gate_t,std::vector<gate_t> > var2mulinput;
911 for(auto mul: mulinputs) {
912 var2mulinput[*getWires(mul).begin()].push_back(mul);
913 }
914 mulinputs.clear();
915
916 for(const auto &[var, muls]: var2mulinput)
917 {
918 const unsigned n = muls.size();
919 std::vector<double> cumulated_probs(n);
920 double cumulated_prob=0.;
921
922 for(unsigned i=0; i<n; ++i) {
923 cumulated_prob += getProb(muls[i]);
924 cumulated_probs[i] = cumulated_prob;
925 gates[static_cast<std::underlying_type<gate_t>::type>(muls[i])] = BooleanGate::AND;
926 getWires(muls[i]).clear();
927 }
928
929 std::vector<gate_t> prefix;
930 prefix.reserve(static_cast<unsigned>(log(n)/log(2)+2));
931 if(!almost_equals(cumulated_probs[n-1],1.)) {
932 prefix.push_back(setGate(BooleanGate::IN, cumulated_probs[n-1]));
933 }
934 rewriteMultivaluedGatesRec(muls, cumulated_probs, 0, n-1, prefix);
935 }
936}
937
938gate_t BooleanCircuit::interpretAsDDInternal(gate_t g, std::set<gate_t> &seen, dDNNF &dd) const {
939 gate_t dg{0};
940
941 switch(getGateType(g)) {
942 case BooleanGate::AND:
943 {
944 dg = dd.setGate(BooleanGate::AND);
945 for(const auto &c: getWires(g)) {
946 auto dc = interpretAsDDInternal(c, seen, dd);
947 dd.addWire(dg, dc);
948 }
949 }
950 break;
951
952 case BooleanGate::OR:
953 {
954 dg = dd.setGate(BooleanGate::NOT);
955 auto dng = dd.setGate(BooleanGate::AND);
956 dd.addWire(dg, dng);
957 for(const auto &c: getWires(g)) {
958 auto dc = interpretAsDDInternal(c, seen, dd);
959 auto dnc = dd.setGate(BooleanGate::NOT);
960 dd.addWire(dnc, dc);
961 dd.addWire(dng, dnc);
962 }
963 }
964 break;
965
966 case BooleanGate::NOT:
967 {
968 dg = dd.setGate(BooleanGate::NOT);
969 auto dc = interpretAsDDInternal(getWires(g)[0], seen, dd);
970 dd.addWire(dg, dc);
971 }
972 break;
973
974 case BooleanGate::IN:
975 if(seen.find(g)!=seen.end())
976 throw CircuitException("Not an independent circuit");
977 seen.insert(g);
978 if(getUUID(g).empty())
979 dg = dd.setGate(BooleanGate::IN, getProb(g));
980 else
981 dg = dd.setGate(getUUID(g), BooleanGate::IN, getProb(g));
982 break;
983
987 throw CircuitException("Unsupported gate in interpretAsDD");
988 }
989
990 return dg;
991}
992
994{
995 dDNNF dd;
996 std::set<gate_t> seen;
997
998 dd.setRoot(interpretAsDDInternal(g, seen, dd));
999
1000 return dd;
1001}
1002
1003dDNNF BooleanCircuit::makeDD(gate_t g, const std::string &method, const std::string &args) const
1004{
1005 if(method=="compilation") {
1006 return compilation(g, args);
1007 } else if(method=="tree-decomposition") {
1008 try {
1009 TreeDecomposition td(*this);
1011 *this, g, td}.build();
1012 } catch(TreeDecompositionException &) {
1013 provsql_error("Treewidth greater than %u", TreeDecomposition::MAX_TREEWIDTH);
1014 }
1015 } else {
1016 dDNNF dd;
1017 try {
1018 dd = interpretAsDD(g);
1019 if(provsql_verbose>=20)
1020 provsql_notice("Circuit interpreted as dD, %ld gates", dd.getNbGates());
1021 } catch(CircuitException &) {
1022 try {
1023 TreeDecomposition td(*this);
1025 *this, g, td}.build();
1026 if(provsql_verbose>=20)
1027 provsql_notice("dD obtained by tree decomposition, %ld gates", dd.getNbGates());
1028 } catch(TreeDecompositionException &) {
1029 dd = compilation(g, "d4");
1030 if(provsql_verbose>=20)
1031 provsql_notice("dD obtained by compilation using d4, %ld gates", dd.getNbGates());
1032 }
1033 }
1034
1035 return dd;
1036 }
1037}
static constexpr bool almost_equals(double a, double b)
Check whether two double values are approximately equal.
Boolean provenance circuit with support for knowledge compilation.
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
std::string to_string(gate_t g)
Convert a gate_t to its decimal string representation.
Definition Circuit.h:250
Out-of-line template method implementations for Circuit<gateType>.
std::vector< double > prob
Per-gate probability (for IN gates).
double WeightMC(gate_t g, std::string opt) const
Compute the probability using the weightmc model counter.
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 possibleWorlds(gate_t g) const
Compute the probability by exact enumeration of all possible worlds.
void setProb(gate_t g, double p)
Set the probability for gate g and mark the circuit as probabilistic.
std::set< gate_t > inputs
Set of IN (input) gate IDs.
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 independentEvaluationInternal(gate_t g, std::set< gate_t > &seen) const
Recursive helper for independentEvaluation().
std::string exportCircuit(gate_t g) const
Export the circuit in the textual format expected by external compilers.
std::string Tseytin(gate_t g, bool display_prob) const
Generate a Tseytin transformation of the sub-circuit at g.
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.
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.
friend class dDNNFTreeDecompositionBuilder
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.
void setInfo(gate_t g, unsigned info)
Store an integer annotation on gate g.
virtual std::string toString(gate_t g) const override
Return a textual description of gate g for debugging.
std::map< gate_t, unsigned > info
Per-gate integer info (for MULIN gates).
dDNNF compilation(gate_t g, std::string compiler) const
Compile the sub-circuit rooted at g to a dDNNF via an external tool.
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.
Exception type thrown by circuit operations on invalid input.
Definition Circuit.h:206
std::string uuid
Definition Circuit.h:65
std::vector< gate_t > & getWires(gate_t g)
Definition Circuit.h:140
BooleanGate getGateType(gate_t g) const
Definition Circuit.h:130
virtual gate_t setGate(const uuid &u, gateType type)
Create or update the gate associated with UUID u.
Definition Circuit.hpp:73
void addWire(gate_t f, gate_t t)
Add a directed wire from gate f (parent) to gate t (child).
Definition Circuit.hpp:81
std::vector< BooleanGate > gates
Definition Circuit.h:71
uuid getUUID(gate_t g) const
Definition Circuit.hpp:46
gate_t getGate(const uuid &u)
Return (or create) the gate associated with UUID u.
Definition Circuit.hpp:33
bool hasGate(const uuid &u) const
Test whether a gate with UUID u exists.
Definition Circuit.hpp:27
std::vector< gate_t >::size_type getNbGates() const
Return the total number of gates in the circuit.
Definition Circuit.h:103
virtual gate_t addGate()
Allocate a new gate with a default-initialised type.
Definition Circuit.hpp:56
Exception thrown when a tree decomposition cannot be constructed.
Tree decomposition of a Boolean circuit's primal graph.
static constexpr int MAX_TREEWIDTH
Maximum supported treewidth.
A d-DNNF circuit supporting exact probabilistic and game-theoretic evaluation.
Definition dDNNF.h:69
void setRoot(gate_t g)
Set the root gate.
Definition dDNNF.h:125
Constructs a d-DNNF from a Boolean circuit and its tree decomposition.
int run_external_tool(const std::string &cmdline)
Run a shell command line, optionally extending PATH.
std::string format_external_tool_status(int rv, const std::string &tool)
Decode a system() return value into a human-readable message.
std::string find_external_tool(const std::string &name)
Locate an external tool by name.
Helpers for invoking external command-line tools.
int provsql_verbose
Verbosity level; controlled by the provsql.verbose_level GUC.
Definition provsql.c:78
int provsql_monte_carlo_seed
Seed for the Monte Carlo sampler; -1 means non-deterministic (std::random_device); controlled by the ...
Definition provsql.c:81
bool provsql_interrupted
Global variable that becomes true if this particular backend received an interrupt signal.
Definition provsql.c:74
Uniform error-reporting macros for ProvSQL.
#define provsql_error(fmt,...)
Report a fatal ProvSQL error and abort the current transaction.
#define provsql_notice(fmt,...)
Emit a ProvSQL informational notice (execution continues).
Core types, constants, and utilities shared across ProvSQL.