61#include <unordered_map>
62#include <unordered_set>
70#define CHECK_FOR_INTERRUPTS() provsql_tdkc_poll()
81inline std::size_t bag_index(
bag_t b)
83 return static_cast<std::underlying_type<bag_t>::type
>(b);
102struct BlockAlternative {
115 std::vector<BlockAlternative> alts;
116 std::vector<unsigned long> endpoints;
131 const std::vector<unsigned long> &from_domain;
144 static constexpr bool tracks_lengths =
false;
145 static constexpr bool has_target_set =
false;
146 static constexpr bool final_collapse =
false;
147 using State = std::bitset<MAXD*MAXD>;
151 std::size_t operator()(
const State &s)
const noexcept {
152 return std::hash<State>()(s);
157 State identity(
int d)
const {
159 for (
int i = 0; i < d; ++i)
164 void close(State &r,
int d)
const {
165 for (
int k = 0; k < d; ++k)
166 for (
int i = 0; i < d; ++i)
168 for (
int j = 0; j < d; ++j)
173 void unite(State &a,
const State &b)
const {
177 void addArc(State &s,
int pu,
int pv,
unsigned )
const {
181 Entry get(
const State &s,
int i,
int j)
const {
182 return s[i*MAXD + j];
185 static bool emptyEntry(Entry e) {
189 void merge(State &s,
int i,
int j, Entry )
const {
193 void seed(State &,
const std::vector<unsigned long> &)
const {
196 void liftExtra(
const State &, State &,
const std::vector<int> &,
197 const LiftContext &)
const {
200 void normalize(State &,
int,
int)
const {
216 static constexpr bool tracks_lengths =
true;
217 static constexpr bool has_target_set =
false;
218 static constexpr bool final_collapse =
false;
219 using Entry = std::uint64_t;
222 std::array<Entry, MAXD*MAXD> m;
231 std::size_t operator()(
const State &s)
const noexcept {
232 std::uint64_t h = 1469598103934665603ull;
233 for (Entry e : s.m) {
235 h *= 1099511628211ull;
237 return static_cast<std::size_t
>(h);
244 explicit HopOps(
unsigned bound)
245 : full(bound >= 63 ? ~Entry(0) : ((Entry(1) << (bound+1)) - 1)) {
249 Entry mink(Entry a, Entry b)
const {
252 const int s = __builtin_ctzll(b);
259 Entry star(Entry diag)
const {
262 const Entry ns = s | mink(s, diag);
270 State identity(
int d)
const {
272 for (
int i = 0; i < d; ++i)
283 void close(State &r,
int d)
const {
284 Entry col[MAXD], row[MAXD];
285 for (
int k = 0; k < d; ++k) {
286 const Entry sk = star(r.m[k*MAXD + k]);
287 for (
int i = 0; i < d; ++i) {
288 col[i] = r.m[i*MAXD + k];
289 row[i] = mink(sk, r.m[k*MAXD + i]);
291 for (
int i = 0; i < d; ++i) {
294 for (
int j = 0; j < d; ++j)
296 r.m[i*MAXD + j] |= mink(col[i], row[j]);
301 void unite(State &a,
const State &b)
const {
302 for (std::size_t i = 0; i < a.m.size(); ++i)
306 void addArc(State &s,
int pu,
int pv,
unsigned weight)
const {
307 const Entry b = (weight < 64) ? (Entry(1) << weight) & full : 0;
308 s.m[pu*MAXD + pv] |= b;
311 Entry get(
const State &s,
int i,
int j)
const {
312 return s.m[i*MAXD + j];
315 static bool emptyEntry(Entry e) {
319 void merge(State &s,
int i,
int j, Entry e)
const {
320 s.m[i*MAXD + j] |= e;
323 void seed(State &,
const std::vector<unsigned long> &)
const {
326 void liftExtra(
const State &, State &,
const std::vector<int> &,
327 const LiftContext &)
const {
330 void normalize(State &,
int,
int)
const {
348 static constexpr bool tracks_lengths =
false;
349 static constexpr bool has_target_set =
true;
350 static constexpr bool final_collapse =
false;
354 std::bitset<MAXD*MAXD> rel;
355 std::bitset<MAXD> dvec;
357 return rel == o.rel && dvec == o.dvec;
362 std::size_t operator()(
const State &s)
const noexcept {
363 return std::hash<std::bitset<MAXD*MAXD> >()(s.rel) * 1099511628211ull
364 ^ std::hash<std::bitset<MAXD> >()(s.dvec);
368 const std::unordered_set<unsigned long> *target_set;
371 State identity(
int d)
const {
373 for (
int i = 0; i < d; ++i)
374 s.rel.set(i*MAXD + i);
378 void close(State &s,
int d)
const {
379 for (
int k = 0; k < d; ++k)
380 for (
int i = 0; i < d; ++i)
381 if (s.rel[i*MAXD + k])
382 for (
int j = 0; j < d; ++j)
383 if (s.rel[k*MAXD + j])
384 s.rel.set(i*MAXD + j);
387 for (
int i = 0; i < d; ++i) {
390 for (
int j = 0; j < d; ++j)
391 if (s.rel[i*MAXD + j] && s.dvec[j]) {
398 void unite(State &a,
const State &b)
const {
403 void addArc(State &s,
int pu,
int pv,
unsigned )
const {
404 s.rel.set(pu*MAXD + pv);
407 Entry get(
const State &s,
int i,
int j)
const {
408 return s.rel[i*MAXD + j];
411 static bool emptyEntry(Entry e) {
415 void merge(State &s,
int i,
int j, Entry )
const {
416 s.rel.set(i*MAXD + j);
419 void seed(State &s,
const std::vector<unsigned long> &domain)
const {
420 for (std::size_t i = 0; i < domain.size(); ++i)
421 if (target_set->count(domain[i]))
422 s.dvec.set(
static_cast<int>(i));
425 void liftExtra(
const State &from, State &to,
426 const std::vector<int> &map,
const LiftContext &)
const {
427 for (std::size_t i = 0; i < map.size(); ++i)
428 if (map[i] >= 0 && from.dvec[
static_cast<int>(i)])
432 void normalize(State &,
int,
int)
const {
472 static constexpr bool tracks_lengths =
false;
473 static constexpr bool has_target_set =
true;
474 static constexpr bool final_collapse =
true;
476 using Mask = std::uint32_t;
477 static_assert(MAXD <= 32,
"rescuer sets are 32-bit masks");
480 std::bitset<MAXD*MAXD> rel;
481 std::vector<Mask> pending;
483 return rel == o.rel && pending == o.pending;
488 std::size_t operator()(
const State &s)
const noexcept {
489 std::size_t h = std::hash<std::bitset<MAXD*MAXD> >()(s.rel);
490 for (Mask m : s.pending)
491 h = h * 1099511628211ull ^ m;
496 const std::unordered_set<unsigned long> *target_set;
499 State identity(
int d)
const {
501 for (
int i = 0; i < d; ++i)
502 s.rel.set(i*MAXD + i);
506 void close(State &s,
int d)
const {
507 for (
int k = 0; k < d; ++k)
508 for (
int i = 0; i < d; ++i)
509 if (s.rel[i*MAXD + k])
510 for (
int j = 0; j < d; ++j)
511 if (s.rel[k*MAXD + j])
512 s.rel.set(i*MAXD + j);
515 void unite(State &a,
const State &b)
const {
517 a.pending.insert(a.pending.end(), b.pending.begin(), b.pending.end());
520 void addArc(State &s,
int pu,
int pv,
unsigned )
const {
521 s.rel.set(pu*MAXD + pv);
524 Entry get(
const State &s,
int i,
int j)
const {
525 return s.rel[i*MAXD + j];
528 static bool emptyEntry(Entry e) {
532 void merge(State &s,
int i,
int j, Entry )
const {
533 s.rel.set(i*MAXD + j);
536 void seed(State &,
const std::vector<unsigned long> &)
const {
539 void liftExtra(
const State &from, State &to,
540 const std::vector<int> &map,
const LiftContext &ctx)
const {
543 for (Mask m : from.pending) {
545 for (std::size_t i = 0; i < map.size(); ++i)
546 if (map[i] >= 0 && (m & (Mask{1} << i)))
547 r |= Mask{1} << map[i];
548 to.pending.push_back(r);
552 for (std::size_t i = 0; i < map.size(); ++i) {
553 if (map[i] >= 0 || !target_set->count(ctx.from_domain[i]))
555 if (from.rel[
static_cast<std::size_t
>(ctx.from_ps)*MAXD + i])
558 for (std::size_t u = 0; u < map.size(); ++u)
559 if (map[u] >= 0 && from.rel[u*MAXD + i])
560 r |= Mask{1} << map[u];
561 to.pending.push_back(r);
569 void normalize(State &s,
int d,
int ps)
const {
570 if (s.pending.empty())
572 for (Mask &m : s.pending) {
575 for (
int u = 0; u < d; ++u) {
576 if (grown & (Mask{1} << u))
578 for (
int w = 0; w < d; ++w)
579 if ((m & (Mask{1} << w)) && s.rel[
static_cast<std::size_t
>(u)*MAXD + w]) {
580 grown |= Mask{1} << u;
588 const Mask ps_bit = Mask{1} << ps;
589 std::vector<Mask> keep;
590 keep.reserve(s.pending.size());
591 for (Mask m : s.pending)
594 std::sort(keep.begin(), keep.end());
595 keep.erase(std::unique(keep.begin(), keep.end()), keep.end());
597 for (Mask m : keep) {
598 bool dominated =
false;
599 for (Mask k : s.pending)
605 s.pending.push_back(m);
639template<
class Ops,
class ReadSink>
640gate_t runReachabilityDP(
const std::vector<ReachabilityCompiler::EdgeRow> &rows,
641 unsigned long source,
643 std::size_t max_states,
644 const unsigned long *only_target,
645 const std::vector<ReachabilityCompiler::SourceArc> *multi_sources,
654 const std::function<
void(
const typename Ops::State &,
668 const std::vector<std::unordered_set<unsigned long> >
669 *multi_sets =
nullptr,
670 const std::function<
void(std::size_t,
671 const typename Ops::State &,
675 using State =
typename Ops::State;
676 using Table = std::unordered_map<State, gate_t, typename Ops::Hash>;
677 using Accumulator = std::unordered_map<State, std::vector<gate_t>,
683 const Ops *opsp = &ops;
689 unsigned long super_source = 0;
691 if (multi_sources->empty())
693 for (
const auto &row : rows)
694 super_source = std::max({super_source, row.src, row.dst});
695 for (
const auto &sa : *multi_sources)
696 super_source = std::max(super_source, sa.vertex);
698 source = super_source;
713 std::vector<EdgeVariable> variables;
714 std::vector<EdgeBlock> blocks;
720 std::unordered_map<std::string, std::size_t> key_to_block;
721 std::unordered_set<std::string> block_tokens;
722 for (
const auto &row : rows) {
723 if (row.block_key.empty())
725 auto [it, fresh] = key_to_block.try_emplace(row.block_key, blocks.size());
728 blk.key = row.block_key;
729 blocks.push_back(std::move(blk));
731 EdgeBlock &blk = blocks[it->second];
732 if (!block_tokens.insert(row.token).second)
734 "provenance token " + row.token +
735 " appears on several block alternatives");
736 BlockAlternative alt;
739 if (row.src != row.dst) {
741 alt.arc_vu = !directed;
745 alt.arc_uv = Ops::tracks_lengths;
748 alt.token = row.token;
750 alt.index = row.block_index;
751 if (alt.arc_uv || alt.arc_vu) {
752 for (
unsigned long e : {row.src, row.dst})
753 if (std::find(blk.endpoints.begin(), blk.endpoints.end(), e) ==
755 blk.endpoints.push_back(e);
757 blk.alts.push_back(std::move(alt));
760 std::unordered_map<std::string, std::size_t> token_to_var;
761 for (
const auto &row : rows) {
762 if (!row.block_key.empty())
764 if (row.src == row.dst && !Ops::tracks_lengths)
766 if (block_tokens.find(row.token) != block_tokens.end())
768 "provenance token " + row.token +
769 " is shared between a block alternative and an edge");
771 auto it = token_to_var.find(row.token);
772 if (it == token_to_var.end()) {
777 var.arc_vu = !directed && row.src != row.dst;
778 var.token = row.token;
780 token_to_var[row.token] = variables.size();
781 variables.push_back(var);
783 EdgeVariable &var = variables[it->second];
784 if (row.src == var.u && row.dst == var.v) {
786 }
else if (row.src == var.v && row.dst == var.u) {
787 var.arc_uv = var.arc_vu =
true;
790 "provenance token " + row.token +
791 " is shared by edges with different endpoints");
803 std::unordered_set<std::string> edge_tokens;
804 for (
const auto &var : variables)
806 edge_tokens.insert(var.token);
807 std::unordered_map<std::string, std::size_t> token_to_var;
808 std::unordered_set<unsigned long> certain_done;
809 for (
const auto &sa : *multi_sources) {
811 if (!certain_done.insert(sa.vertex).second)
814 var.u = super_source;
820 variables.push_back(var);
822 if (edge_tokens.find(sa.token) != edge_tokens.end())
824 "provenance token " + sa.token +
825 " is shared between a source and an edge");
826 auto it = token_to_var.find(sa.token);
827 if (it != token_to_var.end()) {
828 if (variables[it->second].v != sa.vertex)
830 "provenance token " + sa.token +
831 " is shared by sources with different vertices");
835 var.u = super_source;
840 var.token = sa.token;
842 token_to_var[sa.token] = variables.size();
843 variables.push_back(var);
847 for (
const auto &var : variables)
861 for (
const auto &var : variables) {
867 for (
const auto &blk : blocks) {
872 for (std::size_t i = 0; i < blk.endpoints.size(); ++i)
873 for (std::size_t j = i+1; j < blk.endpoints.size(); ++j)
874 graph.
add_edge(blk.endpoints[i], blk.endpoints[j]);
875 if (blk.endpoints.size() == 1)
886 std::unordered_map<unsigned long, bag_t> elimination_bag;
889 stats.
nb_bags = td.getNbBags();
890 const std::size_t nb_bags = td.getNbBags();
896 std::vector<std::vector<std::size_t> > variables_at_bag(nb_bags);
897 for (std::size_t i = 0; i < variables.size(); ++i) {
898 bag_t bu = elimination_bag.at(variables[i].u);
899 bag_t bv = elimination_bag.at(variables[i].v);
900 bag_t b = bag_index(bu) < bag_index(bv) ? bu : bv;
901 variables_at_bag[bag_index(b)].push_back(i);
907 std::vector<std::vector<std::size_t> > blocks_at_bag(nb_bags);
908 for (std::size_t i = 0; i < blocks.size(); ++i) {
909 if (blocks[i].endpoints.empty())
911 bag_t best = elimination_bag.at(blocks[i].endpoints[0]);
912 for (
unsigned long e : blocks[i].endpoints) {
913 bag_t be = elimination_bag.at(e);
914 if (bag_index(be) < bag_index(best))
917 blocks_at_bag[bag_index(best)].push_back(i);
922 std::vector<std::vector<unsigned long> > reads_at_bag(nb_bags);
923 for (
const auto &[v, b] : elimination_bag) {
924 if (multi_sources && v == super_source)
926 if (!only_target || v == *only_target)
927 reads_at_bag[bag_index(b)].push_back(v);
938 const gate_t invalid_gate{
static_cast<std::underlying_type<gate_t>::type
>(-1)};
942 std::vector<gate_t> var_in(variables.size(), invalid_gate);
943 std::vector<gate_t> var_not(variables.size(), invalid_gate);
944 auto inGate = [&](std::size_t i) {
945 if (var_in[i] == invalid_gate)
950 auto notGate = [&](std::size_t i) {
951 if (var_not[i] == invalid_gate) {
953 dd.
addWire(var_not[i], inGate(i));
957 std::vector<gate_t> block_mulvar(blocks.size(), invalid_gate);
958 std::vector<std::vector<gate_t> > block_mulin(blocks.size());
959 std::vector<gate_t> block_none(blocks.size(), invalid_gate);
960 auto mulinGate = [&](std::size_t bi, std::size_t ai) {
961 if (block_mulin[bi].empty())
962 block_mulin[bi].assign(blocks[bi].alts.size(),
964 if (block_mulin[bi][ai] == invalid_gate) {
965 if (block_mulvar[bi] == invalid_gate)
968 const auto &alt = blocks[bi].alts[ai];
972 dd.
addWire(m, block_mulvar[bi]);
973 block_mulin[bi][ai] = m;
975 return block_mulin[bi][ai];
977 auto noneGate = [&](std::size_t bi) {
978 if (block_none[bi] == invalid_gate) {
984 for (std::size_t ai = 0; ai < blocks[bi].alts.size(); ++ai)
985 dd.
addWire(o, mulinGate(bi, ai));
990 return block_none[bi];
1003 const bool consing = multi_sets !=
nullptr && multi_sets->size() > 1;
1005 std::size_t operator()(
const std::pair<std::uint64_t,
1006 std::uint64_t> &p)
const noexcept {
1008 std::uint64_t x = p.first * 0x9e3779b97f4a7c15ull ^ p.second;
1009 x ^= x >> 30; x *= 0xbf58476d1ce4e5b9ull;
1010 x ^= x >> 27; x *= 0x94d049bb133111ebull;
1011 return static_cast<std::size_t
>(x ^ (x >> 31));
1014 std::unordered_map<std::pair<std::uint64_t, std::uint64_t>,
gate_t,
1016 std::unordered_map<std::string, gate_t> cons_or;
1017 auto consKey = [](std::vector<gate_t> children) {
1018 std::sort(children.begin(), children.end());
1020 key.reserve(children.size() *
sizeof(
gate_t));
1021 for (
gate_t c : children)
1022 key.append(
reinterpret_cast<const char *
>(&c),
1032 std::pair<std::uint64_t, std::uint64_t> key;
1035 static_cast<std::uint64_t
>(
1036 static_cast<std::underlying_type<gate_t>::type
>(a));
1038 static_cast<std::uint64_t
>(
1039 static_cast<std::underlying_type<gate_t>::type
>(b));
1040 key = std::minmax(ai, bi);
1041 auto it = cons_and.find(key);
1042 if (it != cons_and.end())
1050 cons_and.emplace(key, g);
1058 auto finalize = [&](Accumulator &acc) {
1060 t.reserve(acc.size());
1061 for (
auto &entry : acc) {
1062 if (entry.second.size() == 1)
1063 t.emplace(entry.first, entry.second[0]);
1069 key = consKey(entry.second);
1070 auto it = cons_or.find(key);
1071 if (it != cons_or.end()) {
1072 t.emplace(entry.first, it->second);
1078 for (
gate_t c : entry.second)
1081 cons_or.emplace(std::move(key), g);
1082 t.emplace(entry.first, g);
1087 if (t.size() > max_states)
1089 "state space exceeds the per-node bound (" +
1090 std::to_string(max_states) +
1091 "); the data treewidth is too large for "
1092 "reachability compilation");
1101 std::vector<std::vector<unsigned long> > domains(nb_bags);
1102 for (std::size_t b = 0; b < nb_bags; ++b) {
1103 auto &d = domains[b];
1104 d.reserve(td.getBag(
bag_t{b}).size()+1);
1106 d.push_back(
static_cast<std::underlying_type<gate_t>::type
>(g));
1107 d.push_back(source);
1108 std::sort(d.begin(), d.end());
1109 d.erase(std::unique(d.begin(), d.end()), d.end());
1112 auto positionIn = [](
const std::vector<unsigned long> &domain,
unsigned long v) {
1113 return static_cast<int>(
1114 std::lower_bound(domain.begin(), domain.end(), v) -
1117 auto trivialState = [&](
const std::vector<unsigned long> &domain) {
1118 auto s = opsp->identity(
static_cast<int>(domain.size()));
1119 opsp->seed(s, domain);
1122 auto trivialTable = [&](
const std::vector<unsigned long> &domain) {
1123 return Table{{trivialState(domain), true_gate}};
1125 auto isTrivial = [&](
const Table &t,
1126 const std::vector<unsigned long> &domain) {
1133 return t.size() == 1 && t.begin()->second == true_gate &&
1134 t.begin()->first == trivialState(domain);
1142 auto lift = [&](
const Table &t,
const std::vector<unsigned long> &from,
1143 const std::vector<unsigned long> &to) {
1146 const int df =
static_cast<int>(from.size());
1147 const int dt =
static_cast<int>(to.size());
1148 std::vector<int> map(from.size());
1149 for (
int i = 0; i < df; ++i) {
1150 auto it = std::lower_bound(to.begin(), to.end(), from[i]);
1151 map[i] = (it != to.end() && *it == from[i])
1152 ?
static_cast<int>(it - to.begin()) : -1;
1154 State
id = opsp->identity(dt);
1156 const LiftContext ctx{from, positionIn(from, source)};
1157 const int to_ps = positionIn(to, source);
1159 for (
const auto &entry : t) {
1161 for (
int i = 0; i < df; ++i) {
1164 for (
int j = 0; j < df; ++j) {
1167 const auto e = opsp->get(entry.first, i, j);
1168 if (!Ops::emptyEntry(e))
1169 opsp->merge(r, map[i], map[j], e);
1172 opsp->liftExtra(entry.first, r, map, ctx);
1173 opsp->normalize(r, dt, to_ps);
1174 acc[r].push_back(entry.second);
1176 return finalize(acc);
1184 auto join = [&](
const Table &t1,
const Table &t2,
1185 const std::vector<unsigned long> &domain) {
1186 const int d =
static_cast<int>(domain.size());
1187 if (isTrivial(t1, domain))
1189 if (isTrivial(t2, domain))
1191 const int ps = positionIn(domain, source);
1193 for (
const auto &left : t1)
1194 for (
const auto &right : t2) {
1195 CHECK_FOR_INTERRUPTS();
1196 State r = left.first;
1197 opsp->unite(r, right.first);
1199 opsp->normalize(r, d, ps);
1200 acc[std::move(r)].push_back(andGate(left.second,
1203 return finalize(acc);
1208 auto applyEdges = [&](Table table, std::size_t b) {
1209 const auto &domain = domains[b];
1210 const int d =
static_cast<int>(domain.size());
1211 const int ps = positionIn(domain, source);
1212 for (std::size_t vi : variables_at_bag[b]) {
1213 const EdgeVariable &var = variables[vi];
1214 const int pu = positionIn(domain, var.u);
1215 const int pv = positionIn(domain, var.v);
1218 for (
const auto &entry : table) {
1219 State present = entry.first;
1221 opsp->addArc(present, pu, pv, var.weight);
1223 opsp->addArc(present, pv, pu, var.weight);
1224 opsp->close(present, d);
1225 opsp->normalize(present, d, ps);
1232 acc[present].push_back(entry.second);
1235 if (present == entry.first) {
1240 acc[entry.first].push_back(entry.second);
1242 acc[present].push_back(
1243 andGate(entry.second, inGate(vi)));
1244 acc[entry.first].push_back(
1245 andGate(entry.second, notGate(vi)));
1248 table = finalize(acc);
1250 for (std::size_t bi : blocks_at_bag[b]) {
1251 const EdgeBlock &blk = blocks[bi];
1253 for (
const auto &entry : table) {
1259 std::vector<State> outs(blk.alts.size());
1260 bool all_same =
true;
1261 for (std::size_t ai = 0; ai < blk.alts.size(); ++ai) {
1262 State present = entry.first;
1263 const auto &alt = blk.alts[ai];
1264 if (alt.arc_uv || alt.arc_vu) {
1265 const int pu = positionIn(domain, alt.u);
1266 const int pv = positionIn(domain, alt.v);
1268 opsp->addArc(present, pu, pv, 1);
1270 opsp->addArc(present, pv, pu, 1);
1271 opsp->close(present, d);
1272 opsp->normalize(present, d, ps);
1275 if (!(present == entry.first))
1279 acc[entry.first].push_back(entry.second);
1282 acc[entry.first].push_back(
1283 andGate(entry.second, noneGate(bi)));
1284 for (std::size_t ai = 0; ai < blk.alts.size(); ++ai)
1285 acc[outs[ai]].push_back(
1286 andGate(entry.second, mulinGate(bi, ai)));
1288 table = finalize(acc);
1298 auto runBottomUp = [&]() {
1299 std::vector<Table> below(nb_bags);
1302 std::size_t next_child = 0;
1304 bool has_table =
false;
1305 explicit Frame(
bag_t b) : bag(b) {
1309 std::vector<Frame> stack;
1310 stack.push_back(Frame(td.getRoot()));
1312 while (!stack.empty()) {
1313 Frame &frame = stack.back();
1314 const auto &children = td.getChildren(frame.bag);
1316 if (frame.next_child < children.size()) {
1317 bag_t c = children[frame.next_child++];
1318 stack.push_back(Frame(c));
1322 CHECK_FOR_INTERRUPTS();
1324 const std::size_t b = bag_index(frame.bag);
1325 Table table = frame.has_table
1326 ? std::move(frame.table)
1327 : trivialTable(domains[b]);
1328 below[b] = applyEdges(std::move(table), b);
1330 if (stack.size() == 1) {
1336 Frame &parent = stack[stack.size()-2];
1337 const std::size_t pb = bag_index(parent.bag);
1338 Table lifted = lift(below[b], domains[b],
1340 if (!parent.has_table) {
1341 parent.table = std::move(lifted);
1342 parent.has_table =
true;
1344 parent.table =
join(parent.table, lifted,
1359 if constexpr (Ops::has_target_set) {
1360 const std::size_t rb = bag_index(td.getRoot());
1361 const int ps = positionIn(domains[rb], source);
1362 const std::vector<unsigned long> source_domain{source};
1363 Ops per_set_ops = ops;
1364 opsp = &per_set_ops;
1365 for (std::size_t si = 0; si < multi_sets->size(); ++si) {
1366 CHECK_FOR_INTERRUPTS();
1367 per_set_ops.target_set = &(*multi_sets)[si];
1368 const std::vector<Table> below = runBottomUp();
1369 if constexpr (Ops::final_collapse) {
1374 const Table collapsed =
1375 lift(below[rb], domains[rb], source_domain);
1376 for (
const auto &[R, g] : collapsed)
1377 (*multi_sink)(si, R, g, 0);
1379 for (
const auto &[R, g] : below[rb])
1380 (*multi_sink)(si, R, g, ps);
1387 "multi-set mode requires a set-seeded state algebra");
1391 const std::vector<Table> below = runBottomUp();
1400 const std::size_t rb = bag_index(td.getRoot());
1401 const int ps = positionIn(domains[rb], source);
1402 for (
const auto &[R, g] : below[rb])
1403 (*root_sink)(R, g, ps);
1416 std::vector<Table> above(nb_bags);
1417 const std::size_t rb = bag_index(td.getRoot());
1418 above[rb] = trivialTable(domains[rb]);
1420 std::vector<bag_t> stack{td.getRoot()};
1421 while (!stack.empty()) {
1422 const bag_t nu = stack.back();
1424 CHECK_FOR_INTERRUPTS();
1425 const std::size_t b = bag_index(nu);
1426 const int d =
static_cast<int>(domains[b].size());
1431 if (!reads_at_bag[b].empty()) {
1432 const int ps = positionIn(domains[b], source);
1433 for (
const auto &[R, g] : below[b])
1434 for (
const auto &[A, h] : above[b]) {
1435 CHECK_FOR_INTERRUPTS();
1437 opsp->unite(closed, A);
1438 opsp->close(closed, d);
1439 gate_t pair_gate = invalid_gate;
1440 for (
unsigned long v : reads_at_bag[b]) {
1441 const auto e = opsp->get(closed, ps, positionIn(domains[b], v));
1442 if (Ops::emptyEntry(e))
1444 if (pair_gate == invalid_gate)
1445 pair_gate = andGate(g, h);
1446 onRead(v, e, pair_gate);
1453 const auto &children = td.getChildren(nu);
1454 const std::size_t m = children.size();
1456 std::vector<Table> lifted(m);
1457 for (std::size_t i = 0; i < m; ++i)
1458 lifted[i] = lift(below[bag_index(children[i])],
1459 domains[bag_index(children[i])], domains[b]);
1461 std::vector<Table> prefix(m+1), suffix(m+1);
1462 prefix[0] = trivialTable(domains[b]);
1463 for (std::size_t i = 0; i < m; ++i)
1464 prefix[i+1] =
join(prefix[i], lifted[i], domains[b]);
1465 suffix[m] = trivialTable(domains[b]);
1466 for (std::size_t i = m; i-- > 0; )
1467 suffix[i] =
join(lifted[i], suffix[i+1], domains[b]);
1469 for (std::size_t i = 0; i < m; ++i) {
1470 Table siblings =
join(prefix[i], suffix[i+1], domains[b]);
1471 Table a = applyEdges(
join(above[b], siblings, domains[b]), b);
1472 const std::size_t cb = bag_index(children[i]);
1473 above[cb] = lift(a, domains[b], domains[cb]);
1474 stack.push_back(children[i]);
1491gate_t finalizeRoot(
dDNNF &dd,
const std::vector<gate_t> &gates)
1493 if (gates.size() == 1)
1506 const std::vector<ReachabilityCompiler::EdgeRow> &rows,
1507 unsigned long source,
1510 std::size_t max_states,
1511 const std::vector<ReachabilityCompiler::SourceArc> *multi_sources)
1515 "hop bound exceeds the supported maximum (" +
1520 const HopOps ops(hop_bound);
1525 std::map<std::pair<unsigned long, unsigned>, std::vector<gate_t> > hop_acc;
1526 std::map<unsigned long, std::vector<gate_t> > within_acc;
1528 const gate_t true_gate = runReachabilityDP(
1529 rows, source, directed, max_states,
nullptr, multi_sources, ops, dd,
1531 [&](
unsigned long v, HopOps::Entry mask,
gate_t pair_gate) {
1532 within_acc[v].push_back(pair_gate);
1534 const unsigned h = static_cast<unsigned>(__builtin_ctzll(mask));
1536 hop_acc[{v, h}].push_back(pair_gate);
1540 result.
roots.reserve(hop_acc.size());
1541 for (
const auto &[key, gates] : hop_acc)
1542 result.
roots.push_back(
1544 finalizeRoot(dd, gates)});
1546 for (
const auto &[v, gates] : within_acc)
1569 const std::vector<ReachabilityCompiler::EdgeRow> &rows,
1570 unsigned long source,
1572 std::size_t max_states,
1573 const unsigned long *only_target,
1574 const std::vector<ReachabilityCompiler::SourceArc> *multi_sources)
1580 std::map<unsigned long, std::vector<gate_t> > accepting;
1581 const gate_t true_gate = runReachabilityDP(
1582 rows, source, directed, max_states, only_target, multi_sources, ops, dd,
1584 [&](
unsigned long v, BoolOps::Entry,
gate_t pair_gate) {
1585 accepting[v].push_back(pair_gate);
1588 result.
roots.reserve(accepting.size());
1589 for (
const auto &[v, gates] : accepting)
1590 result.
roots.push_back(
1608 const std::vector<ReachabilityCompiler::EdgeRow> &rows,
1609 const std::vector<ReachabilityCompiler::SourceArc> &sources,
1610 const std::vector<std::vector<unsigned long> > &sets,
1612 std::size_t max_states)
1626 std::unordered_set<unsigned long> universe;
1627 for (
const auto &row : rows) {
1628 universe.insert(row.src);
1629 universe.insert(row.dst);
1631 for (
const auto &sa : sources)
1632 universe.insert(sa.vertex);
1633 std::vector<std::unordered_set<unsigned long> > target_sets(sets.size());
1634 for (std::size_t si = 0; si < sets.size(); ++si)
1635 for (
unsigned long v : sets[si])
1636 if (universe.count(v))
1637 target_sets[si].insert(v);
1639 ops.target_set = &target_sets[0];
1644 std::vector<std::vector<gate_t> > accepting(sets.size());
1645 const std::function<void(std::size_t,
const SetReachOps::State &,
1647 [&](std::size_t si,
const SetReachOps::State &state,
gate_t g,
int ps) {
1649 accepting[si].push_back(g);
1653 rows, 0, directed, max_states,
nullptr, &sources, ops, dd,
1655 [](
unsigned long, SetReachOps::Entry,
gate_t) {
1657 nullptr, &target_sets, &sink);
1659 result.
roots.reserve(sets.size());
1660 for (std::size_t si = 0; si < sets.size(); ++si) {
1662 if (accepting[si].empty()) {
1667 root = finalizeRoot(dd, accepting[si]);
1668 result.
roots.push_back(root);
1683 const std::vector<ReachabilityCompiler::EdgeRow> &rows,
1684 const std::vector<ReachabilityCompiler::SourceArc> &sources,
1685 const std::vector<std::vector<unsigned long> > &sets,
1687 std::size_t max_states)
1702 std::unordered_set<unsigned long> universe;
1703 for (
const auto &row : rows)
1704 if (row.src != row.dst) {
1705 universe.insert(row.src);
1706 universe.insert(row.dst);
1708 for (
const auto &sa : sources)
1709 universe.insert(sa.vertex);
1710 std::vector<std::unordered_set<unsigned long> > target_sets(sets.size());
1711 std::vector<bool> absent(sets.size(),
false);
1712 for (std::size_t si = 0; si < sets.size(); ++si)
1713 for (
unsigned long v : sets[si]) {
1714 if (universe.count(v))
1715 target_sets[si].insert(v);
1720 ops.target_set = &target_sets[0];
1725 std::vector<std::vector<gate_t> > accepting(sets.size());
1726 const std::function<void(std::size_t,
const CoverOps::State &,
1728 [&](std::size_t si,
const CoverOps::State &state,
gate_t g, int) {
1729 if (state.pending.empty())
1730 accepting[si].push_back(g);
1734 rows, 0, directed, max_states,
nullptr, &sources, ops, dd,
1736 [](
unsigned long, CoverOps::Entry,
gate_t) {
1738 nullptr, &target_sets, &sink);
1740 result.
roots.reserve(sets.size());
1741 for (std::size_t si = 0; si < sets.size(); ++si) {
1743 if (absent[si] || accepting[si].empty()) {
1748 root = finalizeRoot(dd, accepting[si]);
1749 result.
roots.push_back(root);
1759 const std::vector<EdgeRow> &rows,
1760 unsigned long source,
1762 std::size_t max_states)
1764 return compileAllBoolInternal(rows, source, directed, max_states,
nullptr,
1769 const std::vector<EdgeRow> &rows,
1770 const std::vector<SourceArc> &sources,
1772 std::size_t max_states)
1774 return compileAllBoolInternal(rows, 0, directed, max_states,
nullptr,
1779 const std::vector<EdgeRow> &rows,
1780 unsigned long source,
1783 std::size_t max_states)
1785 return compileAllHopsInternal(rows, source, directed, hop_bound, max_states,
1790 const std::vector<EdgeRow> &rows,
1791 const std::vector<SourceArc> &sources,
1794 std::size_t max_states)
1796 return compileAllHopsInternal(rows, 0, directed, hop_bound, max_states,
1801 const std::vector<EdgeRow> &rows,
1802 const std::vector<SourceArc> &sources,
1803 const std::vector<unsigned long> &set,
1805 std::size_t max_states)
1808 compileAnyReachAllInternal(rows, sources, {set}, directed, max_states);
1810 result.
dd = std::move(all.
dd);
1811 result.stats = all.
stats;
1816 const std::vector<EdgeRow> &rows,
1817 const std::vector<SourceArc> &sources,
1818 const std::vector<std::vector<unsigned long> > &sets,
1820 std::size_t max_states)
1822 return compileAnyReachAllInternal(rows, sources, sets, directed, max_states);
1826 const std::vector<EdgeRow> &rows,
1827 const std::vector<SourceArc> &sources,
1828 const std::vector<unsigned long> &set,
1830 std::size_t max_states)
1833 compileCoverReachAllInternal(rows, sources, {set}, directed, max_states);
1835 result.
dd = std::move(all.
dd);
1836 result.stats = all.
stats;
1841 const std::vector<EdgeRow> &rows,
1842 const std::vector<SourceArc> &sources,
1843 const std::vector<std::vector<unsigned long> > &sets,
1845 std::size_t max_states)
1847 return compileCoverReachAllInternal(rows, sources, sets, directed,
1852 const std::vector<EdgeRow> &rows,
1853 unsigned long source,
1854 unsigned long target,
1856 std::size_t max_states)
1858 AllResult all = compileAllBoolInternal(rows, source, directed, max_states,
1865 for (
const auto &vr : all.
roots)
1866 if (vr.vertex == target) {
1878 result.
dd = std::move(all.
dd);
constexpr unsigned DNNF_CERT_INFO
d-DNNF certificate value for the (gate-type-specific) per-gate info field.
@ 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.
@ MULIN
Multivalued-input gate (one of several options).
gate_t
Strongly-typed gate identifier.
Decomposition-aligned compilation of two-terminal reachability over bounded-treewidth data into a d-D...
bag_t
Strongly-typed bag identifier for a tree decomposition.
gate_t setGate(BooleanGate type) override
Allocate a new gate with type type and no UUID.
void setInfo(gate_t g, unsigned info)
Store an integer annotation on gate g.
void addWire(gate_t f, gate_t t)
Add a directed wire from gate f (parent) to gate t (child).
std::vector< gate_t >::size_type getNbGates() const
Return the total number of gates in the circuit.
Mutable adjacency-list graph over unsigned-long node IDs.
void add_edge(unsigned long src, unsigned long tgt, bool undirected=true)
Add an edge between src and tgt.
void add_node(unsigned long node)
Add node to the graph (no edges).
Exception thrown when reachability compilation fails.
static AllHopsResult compileAllHops(const std::vector< EdgeRow > &rows, unsigned long source, bool directed, unsigned hop_bound, std::size_t max_states=DEFAULT_MAX_STATES)
Bounded-hop variant of compileAll(): per-(vertex, exact walk length) circuits for every length up to ...
static Result compile(const std::vector< EdgeRow > &rows, unsigned long source, unsigned long target, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Compile s-t reachability over rows into a d-D.
static constexpr unsigned MAX_HOP_BOUND
Maximum supported hop bound for compileAllHops().
static Result compileCoverReach(const std::vector< EdgeRow > &rows, const std::vector< SourceArc > &sources, const std::vector< unsigned long > &set, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Compile "every vertex of @p set is reachable" (k-terminal / coverage reachability) into one certified...
static Result compileAnyReach(const std::vector< EdgeRow > &rows, const std::vector< SourceArc > &sources, const std::vector< unsigned long > &set, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Compile "some vertex of @p set is reachable" into one certified circuit.
static AnyReachAllResult compileAnyReachAll(const std::vector< EdgeRow > &rows, const std::vector< SourceArc > &sources, const std::vector< std::vector< unsigned long > > &sets, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Multi-set variant of compileAnyReach(): one shared circuit, one root per target set.
static AnyReachAllResult compileCoverReachAll(const std::vector< EdgeRow > &rows, const std::vector< SourceArc > &sources, const std::vector< std::vector< unsigned long > > &sets, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Multi-set variant of compileCoverReach(): one shared (content-deduplicated) circuit,...
static AllResult compileAll(const std::vector< EdgeRow > &rows, unsigned long source, bool directed, std::size_t max_states=DEFAULT_MAX_STATES)
Compile the reachability circuits of every vertex in one pass.
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.
void setRoot(gate_t g)
Set the root gate.
bool operator==(const pg_uuid_t &u, const pg_uuid_t &v)
Test two pg_uuid_t values for equality.
A bounded-hop all-targets compilation.
std::vector< VertexHopRoot > roots
Per (vertex, exact length) roots.
std::vector< VertexRoot > within_roots
Per-vertex "within the bound" roots.
Stats stats
Compilation statistics.
An all-targets compilation: one shared d-D, one root per reachable vertex.
std::vector< VertexRoot > roots
One entry per vertex reachable in the all-edges-present world (including the source itself,...
Stats stats
Compilation statistics.
dDNNF dd
Shared circuit (gates are reused across vertices).
A multi-set any-reach compilation: one shared circuit, one root per target set.
dDNNF dd
Shared circuit (consed: identical subcircuits are the same gate).
Stats stats
Compilation statistics (max_states maxed over the sweeps).
std::vector< gate_t > roots
One root per input set, in input order.
A compiled reachability query: the d-D and its statistics.
Stats stats
Compilation statistics.
dDNNF dd
d-D whose root computes "t is reachable from s"; input gates carry the edge tokens (UUID) and probabi...
Structural statistics of a compilation, for diagnostics and tests.
std::size_t nb_gates
Number of gates of the emitted d-D.
std::size_t nb_bags
Number of bags of the decomposition.
std::size_t max_states
Maximum number of DP states at any node.
std::size_t nb_variables
Number of edge variables (provenance tokens).
unsigned data_treewidth
Treewidth of the min-fill decomposition of the data graph.
One (vertex, walk length) circuit of a bounded-hop compilation.
One vertex's reachability circuit in an all-targets compilation.
Build-loop interrupt hook for the standalone tdkc binary.