17#include <system_error>
18#include <unordered_set>
36constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
55std::string double_to_text(
double v)
57 std::array<char, 32> buf;
58 auto [ptr, ec] = std::to_chars(buf.data(), buf.data() + buf.size(), v);
59 if (ec == std::errc{})
return std::string(buf.data(), ptr);
60 std::ostringstream oss;
61 oss << std::setprecision(17) << v;
79double try_eval_constant(
const GenericCircuit &gc,
gate_t g)
84 catch (
const CircuitException &) {
return NaN; }
90 if (wires.empty())
return NaN;
92 double first = try_eval_constant(gc, wires[0]);
93 if (std::isnan(first))
return NaN;
98 for (std::size_t i = 1; i < wires.size(); ++i) {
99 double v = try_eval_constant(gc, wires[i]);
100 if (std::isnan(v))
return NaN;
107 for (std::size_t i = 1; i < wires.size(); ++i) {
108 double v = try_eval_constant(gc, wires[i]);
109 if (std::isnan(v))
return NaN;
115 if (wires.size() != 2)
return NaN;
116 double v = try_eval_constant(gc, wires[1]);
117 if (std::isnan(v))
return NaN;
121 if (wires.size() != 2)
return NaN;
122 double v = try_eval_constant(gc, wires[1]);
123 if (std::isnan(v))
return NaN;
127 if (wires.size() != 1)
return NaN;
145bool subtree_contains_agg(
const GenericCircuit &gc,
gate_t g)
147 std::unordered_set<gate_t> seen;
148 std::stack<gate_t> stk;
150 while (!stk.empty()) {
151 gate_t cur = stk.top(); stk.pop();
152 if (!seen.insert(cur).second)
continue;
166void replace_with_value(GenericCircuit &gc,
gate_t g,
double c)
180void replace_with_normal_rv(GenericCircuit &gc,
gate_t g,
181 double mean,
double sigma)
184 +
"," + double_to_text(sigma));
191void replace_with_erlang_rv(GenericCircuit &gc,
gate_t g,
192 unsigned long k,
double lambda)
195 +
"," + double_to_text(lambda));
209void replace_with_uniform_rv(GenericCircuit &gc,
gate_t g,
210 double lo,
double hi)
213 +
"," + double_to_text(hi));
220bool is_value_equal_to(
const GenericCircuit &gc,
gate_t g,
double target)
224 catch (
const CircuitException &) {
return false; }
243bool try_identity_drop(GenericCircuit &gc,
gate_t g)
249 std::vector<gate_t> kept;
250 kept.reserve(wires.size());
252 if (!is_value_equal_to(gc, w, 0.0)) kept.push_back(w);
254 if (kept.size() == wires.size())
return false;
256 replace_with_value(gc, g, 0.0);
259 wires = std::move(kept);
265 if (is_value_equal_to(gc, w, 0.0)) {
266 replace_with_value(gc, g, 0.0);
270 std::vector<gate_t> kept;
271 kept.reserve(wires.size());
273 if (!is_value_equal_to(gc, w, 1.0)) kept.push_back(w);
275 if (kept.size() == wires.size())
return false;
277 replace_with_value(gc, g, 1.0);
280 wires = std::move(kept);
306bool is_invalid(
gate_t g) {
return g == INVALID_GATE; }
332std::optional<LinearTerm>
333decompose_linear_term(
const GenericCircuit &gc,
gate_t g)
340 catch (
const CircuitException &) {
return std::nullopt; }
341 return LinearTerm{INVALID_GATE, 0.0, v};
348 return LinearTerm{g, 1.0, 0.0};
363 return LinearTerm{g, 1.0, 0.0};
379 && wires.size() == 1) {
380 return decompose_linear_term(gc, wires[0]);
384 if (wires.size() != 1)
return std::nullopt;
385 auto inner = decompose_linear_term(gc, wires[0]);
386 if (!inner)
return std::nullopt;
387 return LinearTerm{inner->rv_gate, -inner->a, -inner->b};
391 if (wires.size() != 2)
return std::nullopt;
394 gate_t var_side = INVALID_GATE;
397 catch (
const CircuitException &) {
return std::nullopt; }
401 catch (
const CircuitException &) {
return std::nullopt; }
406 auto inner = decompose_linear_term(gc, var_side);
407 if (!inner)
return std::nullopt;
408 return LinearTerm{inner->rv_gate, c * inner->a, c * inner->b};
431bool try_normal_closure(GenericCircuit &gc,
gate_t g)
434 if (wires.size() < 2)
return false;
436 std::vector<LinearTerm> terms;
437 terms.reserve(wires.size());
439 auto term = decompose_linear_term(gc, w);
440 if (!term)
return false;
444 if (!is_invalid(term->rv_gate)) {
448 terms.push_back(*term);
454 std::unordered_set<gate_t> seen_rvs;
456 for (
const auto &t : terms) {
457 if (is_invalid(t.rv_gate))
continue;
459 if (!seen_rvs.insert(t.rv_gate).second)
return false;
461 if (!has_rv)
return false;
463 double total_mean = 0.0;
464 double total_var = 0.0;
465 for (
const auto &t : terms) {
467 if (is_invalid(t.rv_gate))
continue;
470 const double mu = spec->p1;
471 const double sigma = spec->p2;
472 total_mean += t.a * mu;
473 total_var += t.a * t.a * sigma * sigma;
483 if (total_var <= 0.0)
return false;
485 replace_with_normal_rv(gc, g, total_mean, std::sqrt(total_var));
500bool try_erlang_closure(GenericCircuit &gc,
gate_t g)
503 if (wires.size() < 2)
return false;
512 unsigned long total_shape = 0;
513 std::unordered_set<gate_t> seen;
517 if (!spec)
return false;
519 unsigned long w_shape;
527 if (spec->p1 < 1.0 || spec->p1 != std::floor(spec->p1))
return false;
529 w_shape =
static_cast<unsigned long>(spec->p1);
533 if (!seen.insert(w).second)
return false;
534 if (std::isnan(lambda)) lambda = w_lambda;
535 else if (lambda != w_lambda)
return false;
536 total_shape += w_shape;
539 replace_with_erlang_rv(gc, g, total_shape, lambda);
567bool try_uniform_closure(GenericCircuit &gc,
gate_t g)
570 if (wires.size() < 2)
return false;
572 std::vector<LinearTerm> terms;
573 terms.reserve(wires.size());
575 auto term = decompose_linear_term(gc, w);
576 if (!term)
return false;
577 if (!is_invalid(term->rv_gate)) {
581 terms.push_back(*term);
587 const LinearTerm *uniform =
nullptr;
588 for (
const auto &t : terms) {
589 if (is_invalid(t.rv_gate))
continue;
590 if (uniform)
return false;
593 if (!uniform)
return false;
599 double b_total = 0.0;
600 for (
const auto &t : terms) b_total += t.b;
604 const double a = uniform->a;
605 const double p1 = spec->p1;
606 const double p2 = spec->p2;
607 const double new_lo = (a > 0.0) ? a * p1 + b_total : a * p2 + b_total;
608 const double new_hi = (a > 0.0) ? a * p2 + b_total : a * p1 + b_total;
610 replace_with_uniform_rv(gc, g, new_lo, new_hi);
636bool try_neg_rv(GenericCircuit &gc,
gate_t g)
642 if (wires.size() != 1)
return false;
646 if (!spec)
return false;
648 switch (spec->kind) {
650 replace_with_normal_rv(gc, g, -spec->p1, spec->p2);
653 replace_with_uniform_rv(gc, g, -spec->p2, -spec->p1);
693unsigned apply_rules(GenericCircuit &gc,
gate_t g,
694 bool include_scalar_fold);
713bool try_categorical_mixture_lift(GenericCircuit &gc,
gate_t g,
716 const std::vector<gate_t> &others)
729 catch (
const CircuitException &) {
return false; }
743 const std::vector<gate_t> mw = gc.
getWires(mix_gate);
745 std::vector<gate_t> new_wires;
746 new_wires.reserve(mw.size());
747 new_wires.push_back(key);
748 for (std::size_t i = 1; i < mw.size(); ++i) {
749 const gate_t old_mul = mw[i];
752 catch (
const CircuitException &) {
return false; }
756 const double p = gc.
getProb(old_mul);
757 const auto vi =
static_cast<unsigned>(gc.
getInfos(old_mul).first);
759 key, p, vi, double_to_text(new_v));
760 new_wires.push_back(new_mul);
766bool try_mixture_lift(GenericCircuit &gc,
gate_t g,
767 bool include_scalar_fold)
773 if (wires.size() < 2)
return false;
776 std::size_t mix_idx =
static_cast<std::size_t
>(-1);
777 for (std::size_t i = 0; i < wires.size(); ++i) {
779 if (mix_idx !=
static_cast<std::size_t
>(-1))
return false;
783 if (mix_idx ==
static_cast<std::size_t
>(-1))
return false;
785 const auto mix_gate = wires[mix_idx];
790 std::vector<gate_t> others;
791 others.reserve(wires.size() - 1);
792 for (std::size_t i = 0; i < wires.size(); ++i) {
793 if (i != mix_idx) others.push_back(wires[i]);
800 return try_categorical_mixture_lift(gc, g, op, mix_gate, others);
804 const auto &mw = gc.
getWires(mix_gate);
805 if (mw.size() != 3)
return false;
806 const gate_t p_tok = mw[0];
807 const gate_t x_tok = mw[1];
808 const gate_t y_tok = mw[2];
814 std::vector<gate_t> new_x_wires = others; new_x_wires.push_back(x_tok);
815 std::vector<gate_t> new_y_wires = others; new_y_wires.push_back(y_tok);
831 apply_rules(gc, new_x, include_scalar_fold);
832 apply_rules(gc, new_y, include_scalar_fold);
867bool try_times_scalar_rv(GenericCircuit &gc,
gate_t g)
872 if (wires.size() != 2)
return false;
876 gate_t rv_side = INVALID_GATE;
880 catch (
const CircuitException &) {
return false; }
885 catch (
const CircuitException &) {
return false; }
893 if (c == 0.0 || c == 1.0)
return false;
896 if (!spec)
return false;
898 switch (spec->kind) {
900 const double new_mu = c * spec->p1;
901 const double new_sigma = std::fabs(c) * spec->p2;
906 if (new_sigma == 0.0) {
907 replace_with_value(gc, g, new_mu);
909 replace_with_normal_rv(gc, g, new_mu, new_sigma);
914 const double a = spec->p1;
915 const double b = spec->p2;
916 const double lo = (c > 0.0) ? c * a : c * b;
917 const double hi = (c > 0.0) ? c * b : c * a;
918 replace_with_uniform_rv(gc, g, lo, hi);
922 if (c <= 0.0)
return false;
923 const double new_lambda = spec->p1 / c;
924 gc.
resolveToRv(g,
"exponential:" + double_to_text(new_lambda));
928 if (c <= 0.0)
return false;
931 if (spec->p1 < 1.0 || spec->p1 != std::floor(spec->p1))
return false;
932 const auto k =
static_cast<unsigned long>(spec->p1);
933 const double new_lambda = spec->p2 / c;
934 replace_with_erlang_rv(gc, g, k, new_lambda);
970bool try_plus_aggregate(GenericCircuit &gc,
gate_t g,
971 bool include_scalar_fold)
975 const auto &wires_in = gc.
getWires(g);
976 if (wires_in.size() < 2)
return false;
978 std::vector<LinearTerm> terms;
979 terms.reserve(wires_in.size());
980 for (
gate_t w : wires_in) {
981 auto t = decompose_linear_term(gc, w);
982 if (!t)
return false;
989 std::vector<std::pair<gate_t, double>> coeffs;
990 double b_total = 0.0;
991 unsigned constants_in = 0;
992 for (
const auto &t : terms) {
994 if (is_invalid(t.rv_gate)) {
999 for (
auto &p : coeffs) {
1000 if (p.first == t.rv_gate) {
1006 if (!found) coeffs.emplace_back(t.rv_gate, t.a);
1013 const bool has_duplicate = (coeffs.size() < terms.size() - constants_in);
1014 const bool many_constants = (constants_in >= 2);
1015 if (!has_duplicate && !many_constants)
return false;
1018 std::vector<std::pair<gate_t, double>> kept;
1019 kept.reserve(coeffs.size());
1020 for (
const auto &p : coeffs) {
1021 if (p.second != 0.0) kept.push_back(p);
1026 replace_with_value(gc, g, b_total);
1044 if (kept.size() == 1 && b_total == 0.0) {
1045 const auto &only = kept.front();
1046 if (only.second == 1.0) {
1050 double_to_text(only.second));
1058 std::vector<gate_t> new_wires;
1059 new_wires.reserve(kept.size() + 1);
1060 for (
const auto &p : kept) {
1061 if (p.second == 1.0) {
1062 new_wires.push_back(p.first);
1067 new_wires.push_back(tm);
1070 if (b_total != 0.0) {
1074 gc.
setWires(g, std::move(new_wires));
1081 apply_rules(gc, w, include_scalar_fold);
1096unsigned apply_rules(GenericCircuit &gc,
gate_t g,
1097 bool include_scalar_fold)
1104 for (
unsigned iter = 0; iter < 32; ++iter) {
1109 double c = try_eval_constant(gc, g);
1110 if (!std::isnan(c)) {
1111 replace_with_value(gc, g, c);
1131 const auto &wires_in = gc.
getWires(g);
1132 if (wires_in.size() == 2) {
1133 const gate_t a = wires_in[0];
1134 const gate_t b = wires_in[1];
1160 const auto &wires_in = gc.
getWires(g);
1161 if (wires_in.size() == 2 && !subtree_contains_agg(gc, wires_in[0])) {
1162 const double c = try_eval_constant(gc, wires_in[1]);
1163 if (!std::isnan(c) && c != 0.0) {
1164 const gate_t x = wires_in[0];
1166 double_to_text(1.0 / c));
1177 if (try_identity_drop(gc, g)) {
1188 if (try_mixture_lift(gc, g, include_scalar_fold)) {
1201 if (try_plus_aggregate(gc, g, include_scalar_fold)) {
1218 if (try_times_scalar_rv(gc, g)) {
1236 if (try_normal_closure(gc, g)) { ++local;
break; }
1237 if (try_erlang_closure(gc, g)) { ++local;
break; }
1238 if (try_uniform_closure(gc, g)) { ++local;
break; }
1255void simplify(GenericCircuit &gc,
gate_t g,
1256 std::unordered_set<gate_t> &done,
unsigned &counter,
1257 bool include_scalar_fold)
1263 std::stack<std::pair<gate_t, std::size_t>> stk;
1264 if (!done.insert(g).second)
return;
1267 while (!stk.empty()) {
1268 auto &frame = stk.top();
1269 gate_t cur = frame.first;
1270 const auto &wires = gc.
getWires(cur);
1271 if (frame.second < wires.size()) {
1272 gate_t child = wires[frame.second++];
1273 if (done.insert(child).second) stk.emplace(child, 0);
1278 counter += apply_rules(gc, cur, include_scalar_fold);
1287 unsigned counter = 0;
1295 for (std::size_t i = 0; i < nb; ++i) {
1296 auto g =
static_cast<gate_t>(i);
1298 double c = try_eval_constant(gc, g);
1299 if (!std::isnan(c)) {
1300 replace_with_value(gc, g, c);
1309 unsigned counter = 0;
1321 std::unordered_set<gate_t> done;
1323 for (std::size_t i = 0; i < nb; ++i) {
1324 simplify(gc,
static_cast<gate_t>(i), done, counter,
1343 for (std::size_t i = 0; i < nb; ++i) {
1344 auto g =
static_cast<gate_t>(i);
1346 if (try_times_scalar_rv(gc, g)) ++counter;
1347 else if (try_neg_rv(gc, g)) ++counter;
1368 const auto &wires = gc.
getWires(cmp_gate);
1369 if (wires.size() != 2)
return false;
1371 std::unordered_set<gate_t> seen;
1372 std::stack<gate_t> stk;
1375 while (!stk.empty()) {
1376 gate_t g = stk.top(); stk.pop();
1377 if (!seen.insert(g).second)
continue;
1396 if (mw.size() != 3)
return false;
1414void collect_cmp_rv_footprint(
const GenericCircuit &gc,
gate_t cmp_gate,
1415 std::unordered_set<gate_t> &fp)
1417 std::unordered_set<gate_t> seen;
1418 std::stack<gate_t> stk;
1420 while (!stk.empty()) {
1421 gate_t g = stk.top(); stk.pop();
1422 if (!seen.insert(g).second)
continue;
1424 if (t ==
gate_rv) { fp.insert(g);
continue; }
1442 if (mw.size() == 3) { stk.push(mw[1]); stk.push(mw[2]); }
1464constexpr std::size_t JOINT_TABLE_K_MAX = 8;
1478bool is_analytic_singleton_cmp(
const GenericCircuit &gc,
gate_t cmp_gate)
1480 const auto &wires = gc.
getWires(cmp_gate);
1481 if (wires.size() != 2)
return false;
1520struct FastPathInfo {
1522 std::vector<ComparisonOperator> ops;
1523 std::vector<double> thresholds;
1569std::optional<FastPathInfo>
1570detect_shared_scalar(
const GenericCircuit &gc,
1571 const std::vector<gate_t> &cmps)
1574 info.ops.reserve(cmps.size());
1575 info.thresholds.reserve(cmps.size());
1579 const auto &wires = gc.
getWires(c);
1580 if (wires.size() != 2)
return std::nullopt;
1584 if (!ok)
return std::nullopt;
1589 return std::nullopt;
1592 double threshold = std::numeric_limits<double>::quiet_NaN();
1595 scalar_side = wires[0];
1597 catch (
const CircuitException &) {
return std::nullopt; }
1599 scalar_side = wires[1];
1601 catch (
const CircuitException &) {
return std::nullopt; }
1602 effective_op = flip_cmp_op(op);
1604 return std::nullopt;
1608 info.scalar = scalar_side;
1610 }
else if (info.scalar != scalar_side) {
1611 return std::nullopt;
1613 info.ops.push_back(effective_op);
1614 info.thresholds.push_back(threshold);
1638void inline_fast_path(GenericCircuit &gc,
1639 const std::vector<gate_t> &cmps,
1640 const FastPathInfo &info,
1646 std::vector<double> ts = info.thresholds;
1647 std::sort(ts.begin(), ts.end());
1648 ts.erase(std::unique(ts.begin(), ts.end()), ts.end());
1649 const std::size_t m = ts.size();
1650 const std::size_t nb_intervals = m + 1;
1664 std::vector<double> interval_probs(nb_intervals, 0.0);
1665 bool analytical =
false;
1669 std::vector<double> cdf_at_boundary(m);
1671 for (std::size_t i = 0; i < m; ++i) {
1672 cdf_at_boundary[i] =
cdfAt(*spec, ts[i]);
1673 if (std::isnan(cdf_at_boundary[i])) { all_ok =
false;
break; }
1676 interval_probs[0] = cdf_at_boundary[0];
1677 for (std::size_t i = 1; i < m; ++i)
1678 interval_probs[i] = cdf_at_boundary[i] - cdf_at_boundary[i - 1];
1679 interval_probs[m] = 1.0 - cdf_at_boundary[m - 1];
1686 for (
double s : draws) {
1687 auto it = std::upper_bound(ts.begin(), ts.end(), s);
1688 std::size_t idx =
static_cast<std::size_t
>(it - ts.begin());
1689 ++interval_probs[idx];
1691 for (
auto &p : interval_probs) p /= samples;
1700 std::vector<unsigned long> outcome_word(nb_intervals, 0);
1701 for (std::size_t i = 0; i < nb_intervals; ++i) {
1703 if (i == 0) point = ts[0] - 1.0;
1704 else if (i == m) point = ts[m - 1] + 1.0;
1705 else point = 0.5 * (ts[i - 1] + ts[i]);
1706 unsigned long w = 0;
1707 for (std::size_t j = 0; j < info.thresholds.size(); ++j) {
1708 if (apply_cmp(point, info.ops[j], info.thresholds[j]))
1711 outcome_word[i] = w;
1717 std::vector<gate_t> mul_for_interval(nb_intervals,
1718 static_cast<gate_t>(-1));
1719 for (std::size_t i = 0; i < nb_intervals; ++i) {
1720 if (interval_probs[i] <= 0.0)
continue;
1721 mul_for_interval[i] =
1723 static_cast<unsigned>(i));
1728 for (std::size_t j = 0; j < cmps.size(); ++j) {
1729 std::vector<gate_t> plus_wires;
1730 plus_wires.reserve(nb_intervals);
1731 for (std::size_t i = 0; i < nb_intervals; ++i) {
1732 if (!(outcome_word[i] & (1ul << j)))
continue;
1733 gate_t mw = mul_for_interval[i];
1734 if (mw ==
static_cast<gate_t>(-1))
continue;
1735 plus_wires.push_back(mw);
1761void inline_joint_table(GenericCircuit &gc,
1762 const std::vector<gate_t> &cmps,
1765 const unsigned k =
static_cast<unsigned>(cmps.size());
1780 const std::size_t nb_outcomes = std::size_t{1} << k;
1781 std::vector<gate_t> mul_for_outcome(nb_outcomes,
1782 static_cast<gate_t>(-1));
1783 for (std::size_t w = 0; w < nb_outcomes; ++w) {
1784 if (probs[w] <= 0.0)
continue;
1785 mul_for_outcome[w] =
1787 static_cast<unsigned>(w));
1792 for (
unsigned i = 0; i < k; ++i) {
1793 std::vector<gate_t> plus_wires;
1794 plus_wires.reserve(nb_outcomes / 2);
1795 for (std::size_t w = 0; w < nb_outcomes; ++w) {
1796 if ((w & (std::size_t{1} << i)) == 0)
continue;
1797 gate_t m = mul_for_outcome[w];
1798 if (m ==
static_cast<gate_t>(-1))
continue;
1799 plus_wires.push_back(m);
1809 if (samples == 0)
return 0;
1818 std::vector<gate_t> cmps;
1819 for (std::size_t i = 0; i < nb; ++i) {
1820 auto g =
static_cast<gate_t>(i);
1827 std::unordered_map<gate_t, std::unordered_set<gate_t>> footprints;
1828 footprints.reserve(cmps.size());
1830 collect_cmp_rv_footprint(gc, c, footprints[c]);
1837 std::vector<std::size_t> parent(cmps.size());
1838 for (std::size_t i = 0; i < cmps.size(); ++i) parent[i] = i;
1839 auto find = [&](std::size_t x) {
1840 while (parent[x] != x) {
1841 parent[x] = parent[parent[x]];
1846 auto unite = [&](std::size_t a, std::size_t b) {
1847 a = find(a); b = find(b);
1848 if (a != b) parent[a] = b;
1850 for (std::size_t i = 0; i < cmps.size(); ++i) {
1851 for (std::size_t j = i + 1; j < cmps.size(); ++j) {
1852 if (find(i) == find(j))
continue;
1853 const auto &fp_i = footprints[cmps[i]];
1854 const auto &fp_j = footprints[cmps[j]];
1855 const auto &small = fp_i.size() < fp_j.size() ? fp_i : fp_j;
1856 const auto &big = fp_i.size() < fp_j.size() ? fp_j : fp_i;
1857 for (
gate_t rv : small) {
1858 if (big.count(rv)) { unite(i, j);
break; }
1864 std::unordered_map<std::size_t, std::vector<gate_t>> groups;
1865 for (std::size_t i = 0; i < cmps.size(); ++i)
1866 groups[find(i)].push_back(cmps[i]);
1868 unsigned resolved = 0;
1869 for (
auto &[root, group] : groups) {
1874 bool all_pristine =
true;
1878 if (!all_pristine)
continue;
1880 if (group.size() == 1) {
1887 if (is_analytic_singleton_cmp(gc, group[0]))
continue;
1904 if (
auto info = detect_shared_scalar(gc, group)) {
1905 inline_fast_path(gc, group, *info, samples);
1906 resolved +=
static_cast<unsigned>(group.size());
1910 if (group.size() > JOINT_TABLE_K_MAX)
continue;
1912 inline_joint_table(gc, group, samples);
1913 resolved +=
static_cast<unsigned>(group.size());
ComparisonOperator cmpOpFromOid(Oid op_oid, bool &ok)
Map a PostgreSQL comparison-operator OID to a ComparisonOperator.
Typed aggregation value, operator, and aggregator abstractions.
ComparisonOperator
SQL comparison operators used in gate_cmp circuit gates.
@ LE
Less than or equal (<=).
@ GE
Greater than or equal (>=).
Closed-form CDF resolution for trivial gate_cmp shapes.
gate_t
Strongly-typed gate identifier.
Analytical expectation / variance / moment evaluator over RV circuits.
Peephole simplifier for continuous gate_arith sub-circuits.
Monte Carlo sampling over a GenericCircuit, RV-aware.
Continuous random-variable helpers (distribution parsing, moments).
std::vector< gate_t > & getWires(gate_t g)
Return a mutable reference to the child-wire list of gate g.
gateType getGateType(gate_t g) const
Return the type of gate g.
std::vector< gate_t >::size_type getNbGates() const
Return the total number of gates in the circuit.
In-memory provenance circuit with semiring-generic evaluation.
void resolveToPlus(gate_t g, std::vector< gate_t > w)
Rewrite an arbitrary gate as a gate_plus over w.
void resolveToCategoricalMixture(gate_t g, std::vector< gate_t > wires_)
Rewrite g in place as a categorical-form gate_mixture over wires ([key, mul_1, ......
void setWires(gate_t g, std::vector< gate_t > w)
Replace the wires of g with w.
gate_t addAnonymousMulinputGateWithValue(gate_t key, double p, unsigned value_index, const std::string &value_text)
Allocate a fresh gate_mulinput labelled with a numeric outcome value carried in extra.
void resolveToRv(gate_t g, const std::string &s)
Rewrite an arbitrary gate as a gate_rv carrying the distribution-spec extra s.
void resolveToMixture(gate_t g, gate_t p_token, gate_t x_token, gate_t y_token)
Rewrite g in place as a gate_mixture over the wires [p_token, x_token, y_token].
gate_t addAnonymousArithGate(provsql_arith_op op, std::vector< gate_t > wires_)
Allocate a fresh gate_arith gate with operator tag op and the given wires.
gate_t addAnonymousValueGate(const std::string &text)
Allocate a fresh gate_value gate carrying the textual scalar text.
bool isCategoricalMixture(gate_t g) const
Test whether g is a categorical-form gate_mixture (the explicit provsql.categorical output).
void setInfos(gate_t g, unsigned info1, unsigned info2)
Set the integer annotation pair for gate g.
std::string getExtra(gate_t g) const
Return the string extra for gate g.
double getProb(gate_t g) const
Return the probability for gate g.
void resolveCmpToBernoulli(gate_t g, double p)
Replace a gate_cmp by a constant Boolean leaf (gate_one for p == 1, gate_zero for p == 0) or by a Ber...
gate_t addAnonymousInputGate(double p)
Allocate a fresh gate_input gate carrying probability p, with a unique synthetic UUID so subsequent B...
std::pair< unsigned, unsigned > getInfos(gate_t g) const
Return the integer annotation pair for gate g.
gate_t addAnonymousMulinputGate(gate_t key, double p, unsigned value_index)
Allocate a fresh gate_mulinput gate with key key, probability p, and value index value_index.
void resolveToValue(gate_t g, const std::string &s)
Rewrite an arbitrary gate as a gate_value carrying the textual extra s.
@ Normal
Normal (Gaussian): p1=μ, p2=σ
@ Exponential
Exponential: p1=λ, p2 unused.
@ Uniform
Uniform on [a,b]: p1=a, p2=b.
@ Erlang
Erlang: p1=k (positive integer), p2=λ.
unsigned runConstantFold(GenericCircuit &gc)
Constant-fold pass over every gate_arith in gc.
double parseDoubleStrict(const std::string &s)
Strictly parse s as a double.
std::vector< double > monteCarloJointDistribution(const GenericCircuit &gc, const std::vector< gate_t > &cmps, unsigned samples)
Estimate the joint distribution of cmps via Monte Carlo.
unsigned runHybridSimplifier(GenericCircuit &gc)
Run the peephole simplifier over gc.
std::vector< double > monteCarloScalarSamples(const GenericCircuit &gc, gate_t root, unsigned samples)
Sample a scalar sub-circuit samples times and return the draws.
std::optional< DistributionSpec > parse_distribution_spec(const std::string &s)
Parse the on-disk text encoding of a gate_rv distribution.
double monteCarloRV(const GenericCircuit &gc, gate_t root, unsigned samples)
Run Monte Carlo on a circuit that may contain gate_rv leaves.
double cdfAt(const DistributionSpec &d, double c)
Closed-form CDF for a basic continuous distribution.
unsigned runHybridDecomposer(GenericCircuit &gc, unsigned samples)
Marginalise unresolved continuous-island gate_cmp gates into Bernoulli gate_input leaves.
Core types, constants, and utilities shared across ProvSQL.
provsql_arith_op
Arithmetic operator tags used by gate_arith.
@ PROVSQL_ARITH_DIV
binary, child0 / child1
@ PROVSQL_ARITH_PLUS
n-ary, sum of children
@ PROVSQL_ARITH_NEG
unary, -child0
@ PROVSQL_ARITH_MINUS
binary, child0 - child1
@ PROVSQL_ARITH_TIMES
n-ary, product of children
@ gate_rv
Continuous random-variable leaf (extra encodes distribution).
@ gate_mixture
Probabilistic mixture: three wires [p_token (gate_input Bernoulli), x_token, y_token]; samples x when...
@ gate_arith
n-ary arithmetic gate over scalar-valued children (info1 holds operator tag)