ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
TreeDecomposition.cpp
Go to the documentation of this file.
1/**
2 * @file TreeDecomposition.cpp
3 * @brief Tree decomposition construction, manipulation, and I/O.
4 *
5 * Implements the @c TreeDecomposition class declared in
6 * @c TreeDecomposition.h:
7 *
8 * - @c TreeDecomposition(const BooleanCircuit&): constructs the tree
9 * decomposition of the circuit's primal graph using the min-fill
10 * elimination-ordering heuristic (@c PermutationStrategy). Throws
11 * @c TreeDecompositionException if the treewidth exceeds @c MAX_TREEWIDTH.
12 * - @c TreeDecomposition(std::istream&): parses a PACE-format
13 * decomposition from a stream.
14 * - Copy constructor and copy-assignment operator.
15 * - @c makeFriendly(): restructures the tree into the friendly normal
16 * form expected by @c dDNNFTreeDecompositionBuilder.
17 * - @c toDot(): GraphViz DOT string for visualising the tree.
18 * - @c operator>>(): stream extraction in PACE format.
19 *
20 * The @c reroot() helper and @c addEmptyBag(), @c addGateToBag(),
21 * @c findGateConnection() are private utilities for tree restructuring.
22 */
23#include <cassert>
24#include <set>
25#include <algorithm>
26#include <string>
27#include <type_traits>
28
29#include "TreeDecomposition.h"
30#include "BooleanCircuit.h"
31#include "PermutationStrategy.h"
33
35{
36 in >> *this;
37}
38
39// This utility function looks for an existing bag to attach a new bag
40// that contains a single gate v
42{
43 for(bag_t i{0}; i<bags.size(); ++i)
44 for(auto g: getBag(i)) {
45 if(g == v)
46 return i;
47 }
48
49 return root;
50}
51
52// Transform a tree decomposition into one that is root-friendly for a
53// given node root, as per the definition page 6 of
54// https://arxiv.org/pdf/1811.02944 ; the transformation implemented is
55// described in Lemma 2.2 of that paper. The only difference is that we
56// do not enforce the tree to be full, as this is not required for
57// correctness; and we do not make it binary but n-ary for a small n, as
58// it is more efficient.
60 // Look for a bag root_connection to attach to the new root
61 auto root_connection = findGateConnection(v);
62
63 // Create the new root and attach it to this root_connection
64 auto new_root = addEmptyBag(root_connection);
65 addGateToBag(v, new_root);
66 reroot(new_root);
67
68 // Make the tree n-ary for a small n
69 auto nb_bags = bags.size();
70 for(bag_t i{0}; i<nb_bags; ++i) {
71 if(getChildren(i).size()<=OPTIMAL_ARITY)
72 continue;
73
74 auto current = i;
75 auto copy_children=getChildren(i);
76 for(int j=copy_children.size()-OPTIMAL_ARITY-1; j>=0; j-=OPTIMAL_ARITY-1) {
77 decltype(copy_children) new_children;
78 new_children.push_back(current);
79 for(auto k{j}; k>=0 && k>j-OPTIMAL_ARITY; --k)
80 new_children.push_back(copy_children[k]);
81 current = addEmptyBag(getParent(current), new_children);
82 for(auto g: getBag(i))
83 addGateToBag(g, current);
84 }
85 }
86
87 // Transform leaves into paths that introduce gates one at a time
88 nb_bags = bags.size();
89 for(bag_t i{0}; i<nb_bags; ++i) {
90 if(getChildren(i).empty()) {
91 auto p = i;
92 for(size_t j = 1; j < getBag(i).size(); ++j) {
93 p = addEmptyBag(p);
95 for(size_t k = 0; k < getBag(i).size() - j; ++k, ++it)
96 addGateToBag(*it, p);
97 }
98 }
99 }
100
101 // Construct for each bag the union of gates in its children
102 std::vector<std::set<gate_t>> gates_in_children(bags.size());
103 auto getChildrenGates = [&gates_in_children](bag_t i) -> auto& {
104 return gates_in_children[static_cast<std::underlying_type<bag_t>::type>(i)];
105 };
106
107 for(bag_t i{0}; i<bags.size(); ++i) {
108 if(i!=root) {
109 for(auto g: getBag(i))
110 getChildrenGates(getParent(i)).insert(g);
111 }
112 }
113
114 // For every gate that is in an internal bag but not in the union of
115 // its children, construct a subtree introducing these gates one at a
116 // time
117 nb_bags = bags.size();
118 for(bag_t i{0}; i<nb_bags; ++i) {
119 if(!getChildren(i).empty()) {
120 Bag intersection;
121 std::vector<gate_t> extra_gates;
122 for(auto g: getBag(i)) {
123 if(getChildrenGates(i).find(g) == getChildrenGates(i).end())
124 extra_gates.push_back(g);
125 else
126 intersection.insert(g);
127 }
128
129 if(!extra_gates.empty()) {
130 getBag(i) = intersection;
131
132 if(getChildren(i).size()==1 && intersection.size()==getChildrenGates(i).size()) {
133 // We can skip one level, to avoid creating a node identical to
134 // the single child
135
136 auto new_bag = addEmptyBag(i);
137 auto gate = extra_gates.back();
138 addGateToBag(gate, new_bag);
139 addGateToBag(gate, i);
140 extra_gates.pop_back();
141 }
142
143 auto b = i;
144 for(auto g: extra_gates) {
145 auto id = addEmptyBag(getParent(b), {b});
146 getBag(id) = getBag(b);
147 addGateToBag(g, id);
148
149 auto single_gate_bag = addEmptyBag(id);
150 addGateToBag(g, single_gate_bag);
151
152 b = id;
153 }
154 }
155 }
156 }
157}
158
160 const std::vector<bag_t> &ch)
161{
162 bag_t id {bags.size()};
163 bags.push_back(Bag());
164 parent.push_back(p);
165 getChildren(p).push_back(id);
166 children.push_back(ch);
167
168 for(auto c: ch) {
169 if(c!=root)
170 getChildren(getParent(c)).erase(std::find(getChildren(getParent(c)).begin(),
171 getChildren(getParent(c)).end(),
172 c));
173 setParent(c,id);
174 }
175
176 return id;
177}
178
183
185{
186 if(bag == root)
187 return;
188
189 for(bag_t b = bag, p = getParent(b), gp; b != root; b = p, p = gp) {
190 gp = getParent(p);
191 setParent(p, b);
192 if(p!=root)
193 getChildren(gp).erase(std::find(getChildren(gp).begin(),
194 getChildren(gp).end(),
195 p));
196 getChildren(b).push_back(p);
197 }
198
199 getChildren(getParent(bag)).erase(std::find(getChildren(getParent(bag)).begin(),
200 getChildren(getParent(bag)).end(),
201 bag));
202 setParent(bag, bag);
203 root = bag;
204}
205
206std::string TreeDecomposition::toDot() const
207{
208 std::string result="digraph circuit{\n graph [rankdir=UD] ;\n";
209
210 for(bag_t i{0}; i < bags.size(); ++i)
211 {
212 result += " " + to_string(i) + " [label=\"{";
213 bool first=true;
214 for(auto gate: getBag(i)) {
215 if(!first)
216 result+=",";
217 else
218 first=false;
219 result += to_string(gate);
220 }
221 result += "}\"];\n";
222
223 if(i!=root)
224 result+=" " + to_string(getParent(i)) + " -> " + to_string(i) + ";\n";
225 }
226
227 result += "}\n";
228
229 return result;
230}
231
232std::istream& operator>>(std::istream& in, TreeDecomposition &td)
233{
234 in >> td.treewidth;
236
237 unsigned long nb_bags;
238 in >> nb_bags;
239
240 td.bags.resize(nb_bags);
241 td.parent.resize(nb_bags);
242 td.children.resize(nb_bags);
243
244 for(bag_t i{0}; i<nb_bags; ++i) {
245 bag_t id_bag;
246 in >> id_bag;
247
248 assert(i==id_bag);
249
250 unsigned nb_gates;
251 in >> nb_gates;
252
253 assert(nb_gates <= td.treewidth+1);
254
255 for(unsigned long j=0; j<nb_gates; ++j) {
256 gate_t g;
257 in >> g;
258
259 td.addGateToBag(g, i);
260 }
261
262 bag_t parent;
263 in >> parent;
264 td.setParent(i, parent);
265 if(parent == i)
266 td.root = i;
267 else
268 td.getChildren(parent).push_back(i);
269
270 unsigned long nb_children;
271 in >> nb_children;
272
273 for(unsigned long j=0; j<nb_children; ++j) {
274 unsigned long child;
275 in >> child;
276
277 // Ignored, we used the parent link instead
278 }
279 }
280
281 return in;
282}
283
284// Taken and adapted from https://github.com/smaniu/treewidth
286{
287 Graph graph(bc);
288
289 PermutationStrategy strategy;
290
291 strategy.init_permutation(graph);
292
293 // Upper bound on size of the bags vector to avoid redimensioning
294 bags.reserve(graph.number_nodes());
295
296 unsigned max_width{0};
297 std::unordered_map<gate_t, bag_t> bag_ids;
298 bag_t bag_id{0};
299
300 //looping greedily through the permutation
301 //we stop when the maximum bag has the same width as the remaining graph
302 //or when we achive the partial decomposition condition
303 while(max_width<graph.number_nodes() && !strategy.empty()){
304 //getting the next node
305 unsigned long node = strategy.get_next();
306 //removing the node from the graph and getting its neighbours
307 std::unordered_set<unsigned long> neigh = graph.remove_node(node);
308 max_width = std::max<unsigned>(neigh.size(), max_width);
309 //we stop as soon as we find bag that is
310 if(max_width>MAX_TREEWIDTH)
312
313 //filling missing edges between the neighbours and recomputing statistics
314 // for relevant nodes in the graph (the neighbours, most of the time)
315 graph.fill(neigh);
316 strategy.recompute(neigh, graph);
317
318 Bag bag;
319 for(auto n: neigh) {
320 bag.insert(gate_t{n});
321 }
322 bag.insert(gate_t{node});
323
324 bag_ids[gate_t{node}] = bag_id++;
325
326 bags.push_back(bag);
327 }
328
329 if(graph.get_nodes().size()>MAX_TREEWIDTH)
331
332 if(graph.number_nodes()>0) {
333 Bag remaining_bag;
334 for(auto n: graph.get_nodes()) {
335 remaining_bag.insert(gate_t{n});
336 }
337 bags.push_back(remaining_bag);
338 }
339
340 parent.resize(bags.size());
341 children.resize(bags.size());
342
343 if(graph.number_nodes()==0)
344 treewidth=max_width;
345 else
346 treewidth = std::max<unsigned>(max_width,graph.number_nodes()-1);
347
348 for(bag_t i{0};i<bags.size()-1;++i){
349 bag_t min_bag{bags.size()-1};
350 for(auto n: getBag(i)) {
351 auto it = bag_ids.find(n);
352 if(it!=bag_ids.end() && it->second!=i)
353 min_bag = std::min(it->second, min_bag);
354 }
355 setParent(i, min_bag);
356 getChildren(min_bag).push_back(i);
357 }
358
359 // Special semantics: a node is its own parent if it is the root
360 root = bag_t{bags.size()-1};
362}
Boolean provenance circuit with support for knowledge compilation.
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:48
Priority-queue-based node-elimination ordering for tree decomposition.
std::istream & operator>>(std::istream &in, TreeDecomposition &td)
Read a tree decomposition in PACE challenge format.
Tree decomposition of a Boolean circuit for knowledge compilation.
std::string to_string(bag_t b)
Convert a bag_t to its decimal string representation.
bag_t
Strongly-typed bag identifier for a tree decomposition.
Boolean circuit for provenance formula evaluation.
Mutable adjacency-list graph over unsigned-long node IDs.
Definition Graph.h:33
void fill(const std::unordered_set< unsigned long > &nodes, bool undirected=true)
Add all missing edges within nodes (clique fill).
Definition Graph.h:152
const std::unordered_set< unsigned long > & get_nodes() const
Return the set of all node IDs in the graph.
Definition Graph.h:232
std::unordered_set< unsigned long > remove_node(unsigned long node)
Remove node and all its incident edges.
Definition Graph.h:96
unsigned long number_nodes() const
Return the number of nodes in the graph.
Definition Graph.h:240
Node-elimination ordering strategy using a priority queue.
unsigned long get_next()
Pop and return the node with the smallest statistic.
virtual void recompute(const std::unordered_set< unsigned long > &nodes, Graph &graph)
Recompute statistics for a subset of nodes and update the queue.
bool empty()
Return true if the queue is empty.
void init_permutation(Graph &graph)
Populate the priority queue with all nodes in graph.
Exception thrown when a tree decomposition cannot be constructed.
Tree decomposition of a Boolean circuit's primal graph.
bag_t findGateConnection(gate_t v) const
Find the bag whose gate set is closest to gate v (for rooting).
bag_t addEmptyBag(bag_t parent, const std::vector< bag_t > &children=std::vector< bag_t >())
Insert a new empty bag as a child of parent.
std::vector< Bag > bags
Bag contents, indexed by bag_t.
void addGateToBag(gate_t g, bag_t b)
Add gate g to the contents of bag b.
flat_set< gate_t, small_vector > Bag
The type of a bag: a small flat set of gate IDs.
void setParent(bag_t b, bag_t p)
Set the parent of bag b to p.
TreeDecomposition()=default
std::string toDot() const
Render the tree decomposition as a GraphViz DOT string.
void reroot(bag_t bag)
Re-root the tree so that bag becomes the root.
static constexpr int OPTIMAL_ARITY
Preferred maximum arity of bags in the friendly form.
std::vector< bag_t > parent
Parent of each bag (root points to itself)
unsigned treewidth
Treewidth of the decomposition.
bag_t getParent(bag_t b) const
Return the parent of bag b.
static constexpr int MAX_TREEWIDTH
Maximum supported treewidth.
std::vector< std::vector< bag_t > > children
Children of each bag.
bag_t root
Identifier of the root bag.
Bag & getBag(bag_t b)
Mutable access to bag b.
void makeFriendly(gate_t root)
Restructure the tree into the friendly normal form.
std::vector< bag_t > & getChildren(bag_t b)
Mutable access to the children of bag b.
Constructs a d-DNNF from a Boolean circuit and its tree decomposition.
decltype(std::begin(std::declval< const storage_t & >())) const_iterator
Const iterator.
Definition flat_set.hpp:47
iterator begin()
Return iterator to the first element.
Definition flat_set.hpp:51
size_t size() const
Return the number of elements in the set.
Definition flat_set.hpp:81
void insert(const K &value)
Insert value if not already present (const-ref overload).
Definition flat_set.hpp:129