ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
dDNNF.cpp
Go to the documentation of this file.
1/**
2 * @file dDNNF.cpp
3 * @brief d-DNNF circuit operations and evaluation algorithms.
4 *
5 * Implements all methods of @c dDNNF declared in @c dDNNF.h:
6 * - @c vars(): set of reachable input (IN) gates.
7 * - @c makeSmooth(): smooth the circuit so every OR gate's children
8 * mention the same variable set.
9 * - @c makeGatesBinary(): binarise n-ary AND/OR gates.
10 * - @c simplify(): constant propagation.
11 * - @c condition() / @c conditionAndSimplify(): fix one variable.
12 * - @c probabilityEvaluation(): exact probability in linear time.
13 * - @c shapley() / @c banzhaf(): power index computation.
14 * - @c topological_order(): DFS topological sort.
15 *
16 * The private helpers @c shapley_delta() and @c shapley_alpha() implement
17 * the polynomial-time Shapley-value algorithm for d-DNNFs.
18 */
19#include "dDNNF.h"
20#include "Circuit.hpp"
21
22#include <unordered_map>
23#include <set>
24#include <stack>
25#include <variant>
26#include <cassert>
27#include <algorithm>
28#include <functional>
29#include <numeric>
30#include <sstream>
31#include <iomanip>
32
33std::unordered_set<gate_t> dDNNF::vars(gate_t root) const
34{
35 // No recursion, so using a stack to handle all children; order of
36 // calls is here irrelevant
37 std::stack<gate_t> to_process;
38 std::unordered_set<gate_t> processed;
39 to_process.push(root);
40
41 std::unordered_set<gate_t> result;
42 while(!to_process.empty())
43 {
44 auto g = to_process.top();
45 to_process.pop();
46
47 if(processed.find(g)==processed.end()) {
49 result.insert(g);
50 else {
51 for(auto c: getWires(g))
52 to_process.push(c);
53 }
54 processed.insert(g);
55 }
56 }
57
58 return result;
59}
60
62{
63 gate_t original_gates_size{gates.size()};
64 // gates.size() might change in the loop, but newly added gates do not
65 // need to be iterated upon
66
67 for(gate_t g = gate_t{0}; g<original_gates_size; ++g) {
68 if(getGateType(g)!=BooleanGate::OR || getWires(g).size()<=1)
69 continue;
70
71 std::unordered_set<gate_t> all;
72 std::vector<std::unordered_set<gate_t> > varss;
73
74 std::for_each(getWires(g).begin(), getWires(g).end(),
75 [&](gate_t x) {
76 varss.push_back(vars(x));
77 });
78 std::for_each(varss.begin(), varss.end(),
79 [&](const auto &s) {
80 all.insert(s.begin(),s.end());
81 });
82
83 for(auto v: all) {
84 bool modified = false;
85 for(size_t i=0; i<varss.size(); ++i) {
86 if(varss[i].find(v)==varss[i].end()) {
87 if(!modified) {
90 addWire(and_gate, getWires(g)[i]);
91 getWires(g)[i]=and_gate;
92 }
93 modified = true;
94 }
95
96 gate_t dummy_or_gate = setGate(BooleanGate::OR);
97 gate_t dummy_not_gate = setGate(BooleanGate::NOT);
98 addWire(dummy_or_gate, v);
99 addWire(dummy_not_gate, v);
100 addWire(dummy_or_gate, dummy_not_gate);
101 addWire(getWires(g)[i],dummy_or_gate);
102 }
103 }
104 }
105 }
106}
107
109{
110 for(gate_t g{0}; g<gates.size(); ++g) {
111 if(getGateType(g)!=type || getWires(g).size()<=2)
112 continue;
113
114 if(getWires(g).size()==3) {
115 const gate_t child = setGate(type);
116 auto &w = getWires(g);
117 for(size_t i=1; i<3; ++i) {
118 addWire(child, w[i]);
119 }
120 w.resize(1);
121 addWire(g, child);
122 } else {
123 const gate_t child1 = setGate(type);
124 const gate_t child2 = setGate(type);
125
126 auto &w = getWires(g);
127 const auto k = w.size();
128
129 for(unsigned i=0; i<k; ++i)
130 if(i<k/2)
131 addWire(child1, w[i]);
132 else
133 addWire(child2, w[i]);
134 w.clear();
135 addWire(g, child1);
136 addWire(g, child2);
137 }
138 }
139}
140
142{
143 if (gates.size() == 0)
144 return 0.;
145
146 // Unfortunately, dDNNFs can be quite deep so we need to simulate
147 // recursion with a heap-based stack, to avoid exhausting the actual
148 // memory stack
149 using RecursionParams = struct {
150 gate_t g;
151 size_t children_processed;
152 double partial_value;
153 };
154 using RecursionResult = double;
155 std::stack<std::variant<RecursionParams,RecursionResult> > stack;
156 stack.emplace(RecursionParams{root,0,0.});
157
158 while(1) {
159 double child_value{0.};
160
161 if(stack.top().index()==1) { // RecursionResult
162 child_value=std::get<1>(stack.top());
163 stack.pop();
164 }
165
166 auto [g, children_processed, partial_value]=std::get<0>(stack.top());
167 stack.pop();
168
169 auto it = probability_cache.find(g);
170
171 if(it!=probability_cache.end()) {
172 if(stack.empty())
173 return it->second;
174 else
175 stack.emplace(it->second);
176 } else {
177 if(children_processed==0) {
178 switch(getGateType(g)) {
179 case BooleanGate::IN:
180 partial_value = getProb(g);
181 break;
182 case BooleanGate::NOT:
183 // Placeholder; the complement is taken when the child's value
184 // comes back (NOT gates descend into their child like AND/OR,
185 // so a NOT over an internal gate -- e.g. the De Morgan
186 // structures interpretAsDD builds for uncertified ORs -- is
187 // evaluated correctly, not read as 1 - prob-array-default).
188 partial_value = 0;
189 break;
190 case BooleanGate::AND:
191 partial_value = 1;
192 break;
193 case BooleanGate::OR:
194 partial_value = 0;
195 break;
196 default:
197 assert(false);
198 }
199 } else {
200 if(getGateType(g) == BooleanGate::AND) {
201 partial_value *= child_value;
202 } else if(getGateType(g) == BooleanGate::NOT) {
203 partial_value = 1-child_value;
204 } else { // BooleanGate::OR
205 partial_value += child_value;
206 }
207 }
208
209 if(children_processed<getWires(g).size()) {
210 stack.emplace(RecursionParams{g,children_processed+1,partial_value});
211 stack.emplace(RecursionParams{getWires(g)[children_processed],0,0.});
212 } else {
213 double result = partial_value;
214 probability_cache[g]=result;
215
216 if(stack.empty())
217 return result;
218 else
219 stack.emplace(result);
220 }
221 }
222 }
223
224 // We return from within the while loop, when the stack is empty
225 assert(false);
226}
227
229 std::unordered_map<gate_t, double> result;
230 std::unordered_map<gate_t, double> prod_one_plus_p;
231
232 // Stack to simulate recursion: contains a pair (node, b) where b
233 // indicates whether this is the beginning (false) or ending (true) of
234 // the processing of a node
235 std::stack<std::pair<gate_t, bool> > stack;
236 stack.emplace(std::make_pair(root, false));
237
238 while(!stack.empty())
239 {
240 auto [node, b] = stack.top();
241 stack.pop();
242
243 if(result.find(node)!=result.end()) {
244 // Already processed, skip
245 continue;
246 }
247
248 switch(getGateType(node)) {
249 case BooleanGate::IN:
250 result[node] = getProb(node);
251 prod_one_plus_p[node] = 1+getProb(node);
252 break;
253
254 case BooleanGate::NOT:
255 if(!b) {
256 stack.push(std::make_pair(node, true));
257 stack.push(std::make_pair(getWires(node)[0], false));
258 } else {
259 auto child = getWires(node)[0];
260 result[node] = prod_one_plus_p[child] - result[child];
261 prod_one_plus_p[node] = prod_one_plus_p[child];
262 }
263 break;
264
265 case BooleanGate::OR:
266 if(!b) {
267 if(getWires(node).size()==0) { // Has to be an OR False gate
268 result[node] = 0.;
269 prod_one_plus_p[node] = 1.;
270 } else {
271 stack.push(std::make_pair(node, true));
272 for(auto c: getWires(node))
273 stack.push(std::make_pair(c, false));
274 }
275 } else {
276 result[node] =
277 std::accumulate(getWires(node).begin(), getWires(node).end(), 0.,
278 [&](auto r, auto g) {
279 return r + result[g];
280 });
281 prod_one_plus_p[node] = prod_one_plus_p[getWires(node)[0]];
282 }
283 break;
284
285 case BooleanGate::AND:
286 if(!b) {
287 if(getWires(node).size()==0) { // Has to be an AND True gate
288 result[node] = 1.;
289 prod_one_plus_p[node] = 1.;
290 } else {
291 stack.push(std::make_pair(node, true));
292 for(auto c: getWires(node))
293 stack.push(std::make_pair(c, false));
294 }
295 } else {
296 result[node] =
297 std::accumulate(getWires(node).begin(), getWires(node).end(), 1.,
298 [&](auto r, auto g) {
299 return r * result[g];
300 });
301 prod_one_plus_p[node] =
302 std::accumulate(getWires(node).begin(), getWires(node).end(), 1.,
303 [&](auto r, auto g) {
304 return r * prod_one_plus_p[g];
305 });
306 }
307 break;
308
312 throw CircuitException("Incorrect gate type");
313 break;
314 }
315 }
316
317 return result[root];
318}
319
320std::unordered_map<gate_t, std::vector<double> > dDNNF::shapley_delta() const {
321 std::unordered_map<gate_t, std::vector<double> > result;
322
323 if(!isProbabilistic())
324 return result;
325
326 // Stack to simulate recursion: contains a pair (node, b) where b
327 // indicates whether this is the beginning (false) or ending (true) of
328 // the processing of a node
329 std::stack<std::pair<gate_t, bool> > stack;
330 stack.emplace(std::make_pair(root, false));
331
332 while(!stack.empty())
333 {
334 auto [node, b] = stack.top();
335 stack.pop();
336
337 if(result.find(node)!=result.end()) {
338 // Already processed, skip
339 continue;
340 }
341
342 switch(getGateType(node)) {
343 case BooleanGate::IN:
344 result[node] = {1-getProb(node), getProb(node)};
345 break;
346
347 case BooleanGate::NOT:
348 case BooleanGate::OR:
349 if(!b) {
350 if(getWires(node).size()==0) // Has to be an OR False gate
351 result[node] = {1};
352 else {
353 stack.push(std::make_pair(node, true));
354 for(auto c: getWires(node))
355 stack.push(std::make_pair(c, false));
356 }
357 } else {
358 result[node] = result[getWires(node)[0]];
359 }
360 break;
361
362 case BooleanGate::AND:
363 {
364 if(!b) {
365 if(getWires(node).size()==0) // Has to be an AND True gate
366 result[node] = {1};
367 else {
368 stack.push(std::make_pair(node, true));
369 for(auto c: getWires(node))
370 stack.push(std::make_pair(c, false));
371 }
372 } else {
373 if(getWires(node).size()==1)
374 result[node] = result[getWires(node)[0]];
375 else {
376 assert(getWires(node).size()==2); // AND has been made binary
377 const auto &r1 = result[getWires(node)[0]];
378 const auto &r2 = result[getWires(node)[1]];
379 const auto n1=r1.size()-1;
380 const auto n2=r2.size()-1;
381 for(size_t k=0; k<=n1+n2; ++k) {
382 double r = 0.;
383 for(size_t k1=std::max(0,static_cast<int>(k-n2)); k1<=std::min(k,n1); ++k1) {
384 r+=r1[k1]*r2[k-k1];
385 }
386 result[node].push_back(r);
387 }
388 }
389 }
390
391 break;
392 }
393
397 throw CircuitException("Incorrect gate type");
398 break;
399 }
400 }
401
402 return result;
403}
404
405/**
406 * @brief Compute the binomial coefficient C(n, k).
407 * @param n Total number of elements.
408 * @param k Number of elements to choose; must satisfy k ≤ n.
409 * @return The binomial coefficient n-choose-k.
410 */
411static long long comb(unsigned n, unsigned k)
412{
413 assert(k<=n);
414
415 if(k == 0)
416 return 1;
417 else if(k > n/2)
418 return comb(n,n-k);
419 else return n * comb(n-1,k-1) / k;
420}
421
422std::vector<std::vector<double> > dDNNF::shapley_alpha() const {
423 std::unordered_map<gate_t, std::vector<double> > delta {shapley_delta()};
424 std::unordered_map<gate_t, std::vector<std::vector<double> > > result;
425
426 // Stack to simulate recursion: contains a pair (node, b) where b
427 // indicates whether this is the beginning (false) or ending (true) of
428 // the processing of a node
429 std::stack<std::pair<gate_t, bool> > stack;
430 stack.emplace(std::make_pair(root, false));
431
432 while(!stack.empty())
433 {
434 auto [node, b] = stack.top();
435 stack.pop();
436
437 if(result.find(node)!=result.end()) {
438 // Already processed, skip
439 continue;
440 }
441
442 switch(getGateType(node)) {
443 case BooleanGate::IN:
444 result[node] = {{0},{0,getProb(node)}};
445 break;
446
447 case BooleanGate::NOT:
448 if(!b) {
449 stack.push(std::make_pair(node, true));
450 stack.push(std::make_pair(getWires(node)[0], false));
451 } else {
452 result[node] = result[getWires(node)[0]];
453 auto k0=isProbabilistic()?0:result[node].size()-1;
454 for(unsigned k=k0; k<result[node].size(); ++k)
455 for(unsigned l=0; l<=k; ++l) {
456 result[node][k][l] *= -1;
457 result[node][k][l] += comb(k,l)*(isProbabilistic()?delta[node][k]:1);
458 }
459 }
460 break;
461
462 case BooleanGate::OR:
463 if(!b) {
464 if(getWires(node).size()==0) // Has to be an OR False gate
465 result[node] = {{0.}};
466 else {
467 stack.push(std::make_pair(node, true));
468 for(auto c: getWires(node))
469 stack.push(std::make_pair(c, false));
470 }
471 } else {
472 result[node] = result[getWires(node)[0]];
473 for(size_t i=1; i<getWires(node).size(); ++i) {
474 const auto &r = result[getWires(node)[i]];
475 auto k0=isProbabilistic()?0:r.size()-1;
476 for(unsigned k=k0; k<r.size(); ++k)
477 for(unsigned l=0; l<r[k].size(); ++l)
478 result[node][k][l]+=r[k][l];
479 }
480 }
481 break;
482
483 case BooleanGate::AND:
484 if(!b) {
485 if(getWires(node).size()==0) // Has to be an AND True gate
486 result[node] = {{1.}};
487 else {
488 stack.push(std::make_pair(node, true));
489 for(auto c: getWires(node))
490 stack.push(std::make_pair(c, false));
491 }
492 } else {
493 if(getWires(node).size()==1)
494 result[node] = result[getWires(node)[0]];
495 else {
496 assert(getWires(node).size()==2); // AND has been made binary
497 const auto &r1 = result[getWires(node)[0]];
498 const auto &r2 = result[getWires(node)[1]];
499 const auto n1=r1.size()-1;
500 const auto n2=r2.size()-1;
501 result[node].resize(n1+n2+1);
502 auto k0=isProbabilistic()?0:n1+n2;
503 for(size_t k=k0; k<=n1+n2; ++k) {
504 result[node][k].resize(k+1);
505 for(size_t l=0; l<=k; ++l) {
506 for(size_t k1=std::max(0,static_cast<int>(k-n2)); k1<=std::min(k,n1); ++k1)
507 for(size_t l1=std::max(0,static_cast<int>(l-k+k1)); l1<=std::min(k1,l); ++l1)
508 result[node][k][l] += r1[k1][l1] * r2[k-k1][l-l1];
509 }
510 }
511 }
512 }
513 break;
514
518 throw CircuitException("Incorrect gate type");
519 break;
520 }
521 }
522
523 return result[root];
524}
525
526double dDNNF::shapley(gate_t var) const {
527 auto cond_pos = condition(var, true);
528 auto cond_neg = condition(var, false);
529
530 auto alpha_pos=cond_pos.shapley_alpha();
531 auto alpha_neg=cond_neg.shapley_alpha();
532
533 double result=0.;
534
535 double k0=isProbabilistic()?0:alpha_pos.size()-1;
536 for(size_t k=k0; k<alpha_pos.size(); ++k)
537 for(size_t l=0; l<=k; ++l) {
538 double pos = alpha_pos[k][l];
539 double neg = alpha_neg[k][l];
540 result += (pos-neg)/comb(k,l)/(k+1);
541 }
542
543 result *= getProb(var);
544
545 // Avoid rounding errors that make expected Shapley value outside of [-1,1]
546 if(result>1.)
547 result=1.;
548 else if(result<-1.)
549 result=-1.;
550
551 return result;
552}
553
554double dDNNF::banzhaf(gate_t var) const {
555 auto cond_pos = condition(var, true);
556 auto cond_neg = condition(var, false);
557
558 auto env_pos=cond_pos.banzhaf_internal();
559 auto env_neg=cond_neg.banzhaf_internal();
560
561 return getProb(var) * (env_pos-env_neg);
562}
563
564dDNNF dDNNF::condition(gate_t var, bool value) const {
565 assert(getGateType(var)==BooleanGate::IN);
566
567 dDNNF result=*this;
568
569 result.setGateType(var, value ? BooleanGate::AND : BooleanGate::OR);
570 result.probability_cache[var] = value?1.:0.;
571 result.inputs.erase(var);
572 auto it = id2uuid.find(var);
573 if(it!=id2uuid.end()) {
574 result.uuid2id.erase(it->second);
575 result.id2uuid.erase(var);
576 }
577
578 return result;
579}
580
581std::vector<gate_t> dDNNF::topological_order(const std::vector<std::vector<gate_t> > &reversedWires) const
582{
583 std::vector<gate_t> result;
584
585 std::stack<gate_t> nodesToProcess;
586 std::vector<size_t> inDegree(wires.size());
587
588 for(size_t g=0; g<wires.size(); ++g)
589 if(!(inDegree[g] = wires[g].size()))
590 nodesToProcess.push(gate_t{g});
591
592 while(!nodesToProcess.empty()) {
593 auto g = nodesToProcess.top();
594 nodesToProcess.pop();
595 result.push_back(g);
596 for(auto p: reversedWires[static_cast<size_t>(g)])
597 if(!(--inDegree[static_cast<size_t>(p)]))
598 nodesToProcess.push(p);
599 }
600
601 return result;
602}
603
605 std::vector<std::vector<gate_t> > reversedWires(gates.size());
606 for(size_t i=0; i<wires.size(); ++i)
607 for(auto g: wires[i])
608 reversedWires[static_cast<size_t>(g)].push_back(gate_t{i});
609
610 for(auto node: topological_order(reversedWires)) {
611 auto &w = wires[static_cast<size_t>(node)];
612
613 switch(getGateType(node)) {
614 case BooleanGate::IN:
615 break;
616
617 case BooleanGate::AND:
618 case BooleanGate::OR:
619 // First, drop same-type empty children (AND of TRUE = TRUE; OR
620 // of FALSE = FALSE) and short-circuit on opposite-type empty
621 // children (AND of FALSE = FALSE; OR of TRUE = TRUE). May leave
622 // `w` with 0 or 1 entry, which the second block then collapses.
623 if(w.size()>1) {
624 bool shorted = false;
625 for(auto c=w.begin(); c!=w.end();) {
626 if(getGateType(*c)==getGateType(node) && getWires(*c).size()==0) {
627 c = w.erase(c);
629 && getWires(*c).size()==0) {
630 setGateType(node, getGateType(*c));
632 w.clear();
633 shorted = true;
634 break;
635 } else {
636 ++c;
637 }
638 }
639 if(shorted) break;
640 }
641 if(w.size()==0) {
643 } else if(w.size()==1) {
644 if(node==getRoot()) {
645 root=w[0];
646 } else {
647 for(auto p: reversedWires[static_cast<size_t>(node)])
648 std::replace(wires[static_cast<size_t>(p)].begin(), wires[static_cast<size_t>(p)].end(), node, w[0]);
649 }
650 w.clear();
651 }
652 break;
653
654 case BooleanGate::NOT:
655 if(getGateType(w[0])==BooleanGate::AND && getWires(w[0]).size()==0) {
657 probability_cache[node]=0.;
658 w.clear();
659 } else if(getGateType(w[0])==BooleanGate::OR && getWires(w[0]).size()==0) {
661 probability_cache[node]=1.;
662 w.clear();
663 }
664 break;
665
669 throw CircuitException("Incorrect gate type");
670 break;
671 }
672 }
673
674 std::vector<bool> used(gates.size());
675 std::stack<gate_t> to_process;
676 to_process.push(root);
677
678 while(!to_process.empty()) {
679 auto g = to_process.top();
680 to_process.pop();
681 used[static_cast<size_t>(g)]=true;
682 for(auto c: wires[static_cast<size_t>(g)])
683 if(!used[static_cast<size_t>(c)])
684 to_process.push(c);
685 }
686
687 size_t newi = 0;
688 std::vector<gate_t> relabel(gates.size());
689 for(size_t i=0; i<gates.size(); ++i)
690 {
691 if(!used[i]) {
692 inputs.erase(gate_t{i});
693 probability_cache.erase(gate_t{i});
694 auto it = id2uuid.find(gate_t{i});
695 if(it!=id2uuid.end()) {
696 uuid2id.erase(it->second);
697 id2uuid.erase(it);
698 }
699 continue;
700 }
701
702 relabel[i]=gate_t{newi};
703
704 if(i!=newi) {
705 gates[newi] = gates[i];
706 wires[newi] = wires[i];
707 prob[newi]=prob[i];
708
709 auto it1 = probability_cache.find(gate_t{i});
710 if(it1!=probability_cache.end()) {
711 probability_cache[gate_t{newi}] = it1->second;
712 probability_cache.erase(it1);
713 }
714
715 auto it2 = id2uuid.find(gate_t{i});
716 if(it2!=id2uuid.end()) {
717 id2uuid[gate_t{newi}] = it2->second;
718 uuid2id[it2->second] = gate_t{newi};
719 id2uuid.erase(it2);
720 }
721
722 if(root==gate_t{i})
723 root=gate_t{newi};
724
725 auto it3 = inputs.find(gate_t{i});
726 if(it3!=inputs.end()) {
727 inputs.insert(gate_t{newi});
728 inputs.erase(it3);
729 }
730 }
731
732 ++newi;
733 }
734
735 gates.resize(newi);
736 wires.resize(newi);
737 prob.resize(newi);
738
739 for(auto &w: wires)
740 for(size_t i=0; i<w.size(); ++i)
741 w[i]=relabel[static_cast<size_t>(w[i])];
742}
743
745{
746 Stats s;
747
748 // Reachable gates from the root (same traversal as toDot).
749 std::set<gate_t> seen;
750 {
751 std::stack<gate_t> stk;
752 stk.push(root);
753 while(!stk.empty()) {
754 gate_t g = stk.top();
755 stk.pop();
756 if(seen.count(g))
757 continue;
758 seen.insert(g);
760 continue;
761 for(auto c : getWires(g))
762 stk.push(c);
763 }
764 }
765
766 for(gate_t g : seen) {
767 auto type = getGateType(g);
768 if(type == BooleanGate::UNDETERMINED)
769 continue;
770 ++s.nodes;
771 switch(type) {
772 case BooleanGate::AND: ++s.and_gates; break;
773 case BooleanGate::OR: ++s.or_gates; break;
774 case BooleanGate::NOT: ++s.not_gates; break;
775 case BooleanGate::IN: ++s.inputs; break;
776 default: break;
777 }
778 for(gate_t c : getWires(g))
779 if(seen.count(c))
780 ++s.edges;
781 // Smoothness: every OR gate's children must mention the same set of
782 // variables. Cheap enough for an introspection call (vars() is a
783 // reachability set per child).
784 if(type == BooleanGate::OR && s.smooth) {
785 const auto &children = getWires(g);
786 for(std::size_t i = 1; i < children.size() && s.smooth; ++i)
787 if(vars(children[i]) != vars(children[0]))
788 s.smooth = false;
789 }
790 }
791
792 // Longest path (in gates) from the root over the reachable DAG,
793 // memoised: depth(g) = 0 for a leaf, else 1 + max child depth.
794 std::unordered_map<gate_t, int, hash_gate_t> depth;
795 std::function<int(gate_t)> longest = [&](gate_t g) -> int {
796 auto it = depth.find(g);
797 if(it != depth.end())
798 return it->second;
799 int d = 0;
800 for(gate_t c : getWires(g))
801 if(seen.count(c))
802 d = std::max(d, 1 + longest(c));
803 depth[g] = d;
804 return d;
805 };
806 s.depth = seen.empty() ? 0 : longest(root);
807
808 return s;
809}
810
811std::string dDNNF::toNNF(
812 const std::function<int(const std::string &)> &var_of_uuid) const
813{
814 // Emit nodes in post-order so a child's NNF index is always smaller
815 // than its parent's; memoise gate -> assigned NNF node index.
816 std::vector<std::string> lines;
817 std::unordered_map<gate_t, int, hash_gate_t> idx;
818 std::size_t edges = 0;
819 int max_var = 0;
820
821 // Variable index of an input gate: the caller-supplied numbering (by
822 // original UUID) when available, else the d-DNNF's own gate id + 1.
823 auto var_of = [&](gate_t g) -> int {
824 if(var_of_uuid) {
825 auto u = id2uuid.find(g);
826 if(u != id2uuid.end()) {
827 int v = var_of_uuid(u->second);
828 if(v > 0)
829 return v;
830 }
831 }
832 return static_cast<int>(
833 static_cast<std::underlying_type<gate_t>::type>(g)) + 1;
834 };
835
836 std::function<int(gate_t)> emit = [&](gate_t g) -> int {
837 auto found = idx.find(g);
838 if(found != idx.end())
839 return found->second;
840
841 std::string line;
842 switch(getGateType(g)) {
843 case BooleanGate::IN: {
844 int v = var_of(g);
845 max_var = std::max(max_var, v);
846 line = "L " + std::to_string(v);
847 break;
848 }
849 case BooleanGate::NOT: {
850 // A literal: NOT must sit directly over an input in NNF. The IN
851 // child is not emitted as a node of its own here -- the negative
852 // literal is the leaf.
853 gate_t c = *getWires(g).begin();
855 throw CircuitException(
856 "toNNF: NOT over a non-input gate (circuit not in negation normal form)");
857 int v = var_of(c);
858 max_var = std::max(max_var, v);
859 line = "L -" + std::to_string(v);
860 break;
861 }
862 case BooleanGate::AND: {
863 std::vector<int> ch;
864 for(gate_t c : getWires(g))
865 ch.push_back(emit(c));
866 edges += ch.size();
867 line = "A " + std::to_string(ch.size());
868 for(int i : ch)
869 line += " " + std::to_string(i);
870 break;
871 }
872 case BooleanGate::OR: {
873 std::vector<int> ch;
874 for(gate_t c : getWires(g))
875 ch.push_back(emit(c));
876 edges += ch.size();
877 // j (decision variable) is 0: ProvSQL's OR gates do not record
878 // which variable the branches split on.
879 line = "O 0 " + std::to_string(ch.size());
880 for(int i : ch)
881 line += " " + std::to_string(i);
882 break;
883 }
884 default:
885 throw CircuitException("toNNF: unexpected gate type in d-DNNF");
886 }
887
888 int my = static_cast<int>(lines.size());
889 lines.push_back(std::move(line));
890 idx[g] = my;
891 return my;
892 };
893
894 emit(root);
895
896 std::ostringstream out;
897 out << "nnf " << lines.size() << " " << edges << " " << max_var << "\n";
898 for(const auto &l : lines)
899 out << l << "\n";
900 return out.str();
901}
902
903std::string dDNNF::toDot() const
904{
905 std::ostringstream out;
906 out << "digraph dDNNF {\n"
907 << " graph [rankdir=TB];\n"
908 << " node [fontname=\"Helvetica\"];\n";
909
910 // Collect reachable gates from the root in deterministic gate-id
911 // order (std::set ordering) so the output is stable across runs.
912 std::stack<gate_t> stk;
913 std::set<gate_t> seen;
914 stk.push(root);
915 while(!stk.empty()) {
916 gate_t g = stk.top();
917 stk.pop();
918 if(seen.count(g))
919 continue;
920 seen.insert(g);
922 continue;
923 for(auto c : getWires(g))
924 stk.push(c);
925 }
926
927 for(auto g : seen) {
928 auto type = getGateType(g);
929 if(type == BooleanGate::UNDETERMINED)
930 continue;
931
932 auto id = static_cast<std::underlying_type<gate_t>::type>(g);
933 std::string label;
934 std::string shape = "circle";
935 std::string fill;
936 std::string tooltip;
937 switch(type) {
938 case BooleanGate::AND:
939 label = "∧"; // ∧
940 break;
941 case BooleanGate::OR:
942 label = "∨"; // ∨
943 break;
944 case BooleanGate::NOT:
945 label = "¬"; // ¬
946 break;
947 case BooleanGate::IN: {
948 shape = "box";
949 fill = ", style=filled, fillcolor=\"#e6f0fa\"";
950 std::string u;
951 auto it = id2uuid.find(g);
952 if(it != id2uuid.end())
953 u = it->second;
954 std::string sh = u.size() > 8 ? u.substr(0, 8) : u;
955 std::ostringstream lab;
956 lab << sh << "\\np=" << std::fixed << std::setprecision(3) << prob[id];
957 label = lab.str();
958 // Surface the full original-circuit UUID through a `tooltip`
959 // attribute so the Studio renderer can look the source row up
960 // via /api/leaf/<uuid> (same behaviour as input gates in the
961 // provenance circuit). The short prefix on the label is purely
962 // for the in-circle text.
963 tooltip = u;
964 break;
965 }
966 default:
967 continue;
968 }
969
970 out << " n" << id << " [label=\"" << label << "\", shape=" << shape
971 << fill;
972 if(!tooltip.empty())
973 out << ", tooltip=\"" << tooltip << "\"";
974 if(g == root)
975 out << ", penwidth=2";
976 out << "];\n";
977 }
978
979 for(auto g : seen) {
980 auto type = getGateType(g);
981 if(type == BooleanGate::IN || type == BooleanGate::UNDETERMINED)
982 continue;
983 auto id = static_cast<std::underlying_type<gate_t>::type>(g);
984 for(auto c : getWires(g)) {
985 if(!seen.count(c))
986 continue;
987 auto cid = static_cast<std::underlying_type<gate_t>::type>(c);
988 out << " n" << id << " -> n" << cid << ";\n";
989 }
990 }
991
992 out << "}\n";
993 return out.str();
994}
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).
std::set< gate_t > inputs
Set of IN (input) gate IDs.
gate_t setGate(BooleanGate type) override
Allocate a new gate with type type and no UUID.
double getProb(gate_t g) const
Return the probability stored for gate g.
bool isProbabilistic() const
Return true if any gate has a non-trivial (< 1) probability.
Exception type thrown by circuit operations on invalid input.
Definition Circuit.h:206
std::vector< gate_t > & getWires(gate_t g)
Definition Circuit.h:140
BooleanGate getGateType(gate_t g) const
Definition Circuit.h:130
std::unordered_map< gate_t, uuid > id2uuid
Definition Circuit.h:69
void addWire(gate_t f, gate_t t)
Definition Circuit.hpp:81
std::unordered_map< uuid, gate_t > uuid2id
UUID string → gate index.
Definition Circuit.h:68
void setGateType(gate_t g, gateType t)
Update the type of an existing gate.
Definition Circuit.h:79
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
std::string toNNF(const std::function< int(const std::string &)> &var_of_uuid={}) const
Serialise the d-DNNF in the c2d / d4 ".nnf" text format.
Definition dDNNF.cpp:811
gate_t root
The root gate of the d-DNNF.
Definition dDNNF.h:113
dDNNF condition(gate_t var, bool value) const
Condition on variable var having value value (no simplification).
Definition dDNNF.cpp:564
double banzhaf_internal() const
Compute the unnormalised Banzhaf value for the whole circuit.
Definition dDNNF.cpp:228
Stats nodeStats() const
Compute structural statistics over the gates reachable from root.
Definition dDNNF.cpp:744
double probabilityEvaluation() const
Compute the exact probability of the d-DNNF being true.
Definition dDNNF.cpp:141
void simplify()
Simplify the d-DNNF by removing redundant constants.
Definition dDNNF.cpp:604
void makeSmooth()
Make the d-DNNF smooth.
Definition dDNNF.cpp:61
void makeGatesBinary(BooleanGate type)
Rewrite all n-ary AND/OR gates into binary trees.
Definition dDNNF.cpp:108
std::vector< std::vector< double > > shapley_alpha() const
Compute the α table used in the Shapley algorithm.
Definition dDNNF.cpp:422
double shapley(gate_t var) const
Compute the Shapley value of input gate var.
Definition dDNNF.cpp:526
double banzhaf(gate_t var) const
Compute the Banzhaf power index of input gate var.
Definition dDNNF.cpp:554
std::vector< gate_t > topological_order(const std::vector< std::vector< gate_t > > &reversedWires) const
Compute a topological ordering of the circuit.
Definition dDNNF.cpp:581
std::unordered_set< gate_t > vars(gate_t root) const
Return the set of all variable (IN) gates reachable from root.
Definition dDNNF.cpp:33
std::unordered_map< gate_t, double, hash_gate_t > probability_cache
Memoisation cache mapping gates to their probability values.
Definition dDNNF.h:74
gate_t getRoot() const
Return the root gate of this d-DNNF.
Definition dDNNF.h:120
std::unordered_map< gate_t, std::vector< double > > shapley_delta() const
Compute the δ table used in the Shapley algorithm.
Definition dDNNF.cpp:320
std::string toDot() const
Return a GraphViz DOT representation of the d-DNNF.
Definition dDNNF.cpp:903
static long long comb(unsigned n, unsigned k)
Compute the binomial coefficient C(n, k).
Definition dDNNF.cpp:411
Decomposable Deterministic Negation Normal Form circuit.
Structural statistics of a compiled d-DNNF.
Definition dDNNF.h:227
bool smooth
Every OR gate's children share their variable set.
Definition dDNNF.h:234
int depth
Longest path (in gates) from the root.
Definition dDNNF.h:235
std::size_t nodes
Total reachable gates.
Definition dDNNF.h:228
std::size_t or_gates
OR (decision) gates.
Definition dDNNF.h:231
std::size_t edges
Total wires among reachable gates.
Definition dDNNF.h:229
std::size_t not_gates
NOT gates.
Definition dDNNF.h:232
std::size_t inputs
IN (variable) leaves.
Definition dDNNF.h:233
std::size_t and_gates
AND (decomposition) gates.
Definition dDNNF.h:230