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;
140void replace_with_value(GenericCircuit &gc,
gate_t g,
double c)
154void replace_with_normal_rv(GenericCircuit &gc,
gate_t g,
155 double mean,
double sigma)
158 +
"," + double_to_text(sigma));
165void replace_with_erlang_rv(GenericCircuit &gc,
gate_t g,
166 unsigned long k,
double lambda)
169 +
"," + double_to_text(lambda));
183void replace_with_uniform_rv(GenericCircuit &gc,
gate_t g,
184 double lo,
double hi)
187 +
"," + double_to_text(hi));
194bool is_value_equal_to(
const GenericCircuit &gc,
gate_t g,
double target)
198 catch (
const CircuitException &) {
return false; }
217bool try_identity_drop(GenericCircuit &gc,
gate_t g)
223 std::vector<gate_t> kept;
224 kept.reserve(wires.size());
226 if (!is_value_equal_to(gc, w, 0.0)) kept.push_back(w);
228 if (kept.size() == wires.size())
return false;
230 replace_with_value(gc, g, 0.0);
233 wires = std::move(kept);
239 if (is_value_equal_to(gc, w, 0.0)) {
240 replace_with_value(gc, g, 0.0);
244 std::vector<gate_t> kept;
245 kept.reserve(wires.size());
247 if (!is_value_equal_to(gc, w, 1.0)) kept.push_back(w);
249 if (kept.size() == wires.size())
return false;
251 replace_with_value(gc, g, 1.0);
254 wires = std::move(kept);
280bool is_invalid(
gate_t g) {
return g == INVALID_GATE; }
306std::optional<LinearTerm>
307decompose_linear_term(
const GenericCircuit &gc,
gate_t g)
314 catch (
const CircuitException &) {
return std::nullopt; }
315 return LinearTerm{INVALID_GATE, 0.0, v};
322 return LinearTerm{g, 1.0, 0.0};
337 return LinearTerm{g, 1.0, 0.0};
353 && wires.size() == 1) {
354 return decompose_linear_term(gc, wires[0]);
358 if (wires.size() != 1)
return std::nullopt;
359 auto inner = decompose_linear_term(gc, wires[0]);
360 if (!inner)
return std::nullopt;
361 return LinearTerm{inner->rv_gate, -inner->a, -inner->b};
365 if (wires.size() != 2)
return std::nullopt;
368 gate_t var_side = INVALID_GATE;
371 catch (
const CircuitException &) {
return std::nullopt; }
375 catch (
const CircuitException &) {
return std::nullopt; }
380 auto inner = decompose_linear_term(gc, var_side);
381 if (!inner)
return std::nullopt;
382 return LinearTerm{inner->rv_gate, c * inner->a, c * inner->b};
405bool try_normal_closure(GenericCircuit &gc,
gate_t g)
408 if (wires.size() < 2)
return false;
410 std::vector<LinearTerm> terms;
411 terms.reserve(wires.size());
413 auto term = decompose_linear_term(gc, w);
414 if (!term)
return false;
418 if (!is_invalid(term->rv_gate)) {
422 terms.push_back(*term);
428 std::unordered_set<gate_t> seen_rvs;
430 for (
const auto &t : terms) {
431 if (is_invalid(t.rv_gate))
continue;
433 if (!seen_rvs.insert(t.rv_gate).second)
return false;
435 if (!has_rv)
return false;
437 double total_mean = 0.0;
438 double total_var = 0.0;
439 for (
const auto &t : terms) {
441 if (is_invalid(t.rv_gate))
continue;
444 const double mu = spec->p1;
445 const double sigma = spec->p2;
446 total_mean += t.a * mu;
447 total_var += t.a * t.a * sigma * sigma;
457 if (total_var <= 0.0)
return false;
459 replace_with_normal_rv(gc, g, total_mean, std::sqrt(total_var));
474bool try_erlang_closure(GenericCircuit &gc,
gate_t g)
477 if (wires.size() < 2)
return false;
486 unsigned long total_shape = 0;
487 std::unordered_set<gate_t> seen;
491 if (!spec)
return false;
493 unsigned long w_shape;
501 if (spec->p1 < 1.0 || spec->p1 != std::floor(spec->p1))
return false;
503 w_shape =
static_cast<unsigned long>(spec->p1);
507 if (!seen.insert(w).second)
return false;
508 if (std::isnan(lambda)) lambda = w_lambda;
509 else if (lambda != w_lambda)
return false;
510 total_shape += w_shape;
513 replace_with_erlang_rv(gc, g, total_shape, lambda);
541bool try_uniform_closure(GenericCircuit &gc,
gate_t g)
544 if (wires.size() < 2)
return false;
546 std::vector<LinearTerm> terms;
547 terms.reserve(wires.size());
549 auto term = decompose_linear_term(gc, w);
550 if (!term)
return false;
551 if (!is_invalid(term->rv_gate)) {
555 terms.push_back(*term);
561 const LinearTerm *uniform =
nullptr;
562 for (
const auto &t : terms) {
563 if (is_invalid(t.rv_gate))
continue;
564 if (uniform)
return false;
567 if (!uniform)
return false;
573 double b_total = 0.0;
574 for (
const auto &t : terms) b_total += t.b;
578 const double a = uniform->a;
579 const double p1 = spec->p1;
580 const double p2 = spec->p2;
581 const double new_lo = (a > 0.0) ? a * p1 + b_total : a * p2 + b_total;
582 const double new_hi = (a > 0.0) ? a * p2 + b_total : a * p1 + b_total;
584 replace_with_uniform_rv(gc, g, new_lo, new_hi);
610bool try_neg_rv(GenericCircuit &gc,
gate_t g)
616 if (wires.size() != 1)
return false;
620 if (!spec)
return false;
622 switch (spec->kind) {
624 replace_with_normal_rv(gc, g, -spec->p1, spec->p2);
627 replace_with_uniform_rv(gc, g, -spec->p2, -spec->p1);
667unsigned apply_rules(GenericCircuit &gc,
gate_t g,
668 bool include_scalar_fold);
687bool try_categorical_mixture_lift(GenericCircuit &gc,
gate_t g,
690 const std::vector<gate_t> &others)
703 catch (
const CircuitException &) {
return false; }
717 const std::vector<gate_t> mw = gc.
getWires(mix_gate);
719 std::vector<gate_t> new_wires;
720 new_wires.reserve(mw.size());
721 new_wires.push_back(key);
722 for (std::size_t i = 1; i < mw.size(); ++i) {
723 const gate_t old_mul = mw[i];
726 catch (
const CircuitException &) {
return false; }
730 const double p = gc.
getProb(old_mul);
731 const auto vi =
static_cast<unsigned>(gc.
getInfos(old_mul).first);
733 key, p, vi, double_to_text(new_v));
734 new_wires.push_back(new_mul);
740bool try_mixture_lift(GenericCircuit &gc,
gate_t g,
741 bool include_scalar_fold)
747 if (wires.size() < 2)
return false;
750 std::size_t mix_idx =
static_cast<std::size_t
>(-1);
751 for (std::size_t i = 0; i < wires.size(); ++i) {
753 if (mix_idx !=
static_cast<std::size_t
>(-1))
return false;
757 if (mix_idx ==
static_cast<std::size_t
>(-1))
return false;
759 const auto mix_gate = wires[mix_idx];
764 std::vector<gate_t> others;
765 others.reserve(wires.size() - 1);
766 for (std::size_t i = 0; i < wires.size(); ++i) {
767 if (i != mix_idx) others.push_back(wires[i]);
774 return try_categorical_mixture_lift(gc, g, op, mix_gate, others);
778 const auto &mw = gc.
getWires(mix_gate);
779 if (mw.size() != 3)
return false;
780 const gate_t p_tok = mw[0];
781 const gate_t x_tok = mw[1];
782 const gate_t y_tok = mw[2];
788 std::vector<gate_t> new_x_wires = others; new_x_wires.push_back(x_tok);
789 std::vector<gate_t> new_y_wires = others; new_y_wires.push_back(y_tok);
805 apply_rules(gc, new_x, include_scalar_fold);
806 apply_rules(gc, new_y, include_scalar_fold);
841bool try_times_scalar_rv(GenericCircuit &gc,
gate_t g)
846 if (wires.size() != 2)
return false;
850 gate_t rv_side = INVALID_GATE;
854 catch (
const CircuitException &) {
return false; }
859 catch (
const CircuitException &) {
return false; }
867 if (c == 0.0 || c == 1.0)
return false;
870 if (!spec)
return false;
872 switch (spec->kind) {
874 const double new_mu = c * spec->p1;
875 const double new_sigma = std::fabs(c) * spec->p2;
880 if (new_sigma == 0.0) {
881 replace_with_value(gc, g, new_mu);
883 replace_with_normal_rv(gc, g, new_mu, new_sigma);
888 const double a = spec->p1;
889 const double b = spec->p2;
890 const double lo = (c > 0.0) ? c * a : c * b;
891 const double hi = (c > 0.0) ? c * b : c * a;
892 replace_with_uniform_rv(gc, g, lo, hi);
896 if (c <= 0.0)
return false;
897 const double new_lambda = spec->p1 / c;
898 gc.
resolveToRv(g,
"exponential:" + double_to_text(new_lambda));
902 if (c <= 0.0)
return false;
905 if (spec->p1 < 1.0 || spec->p1 != std::floor(spec->p1))
return false;
906 const auto k =
static_cast<unsigned long>(spec->p1);
907 const double new_lambda = spec->p2 / c;
908 replace_with_erlang_rv(gc, g, k, new_lambda);
944bool try_plus_aggregate(GenericCircuit &gc,
gate_t g,
945 bool include_scalar_fold)
949 const auto &wires_in = gc.
getWires(g);
950 if (wires_in.size() < 2)
return false;
952 std::vector<LinearTerm> terms;
953 terms.reserve(wires_in.size());
954 for (
gate_t w : wires_in) {
955 auto t = decompose_linear_term(gc, w);
956 if (!t)
return false;
963 std::vector<std::pair<gate_t, double>> coeffs;
964 double b_total = 0.0;
965 unsigned constants_in = 0;
966 for (
const auto &t : terms) {
968 if (is_invalid(t.rv_gate)) {
973 for (
auto &p : coeffs) {
974 if (p.first == t.rv_gate) {
980 if (!found) coeffs.emplace_back(t.rv_gate, t.a);
987 const bool has_duplicate = (coeffs.size() < terms.size() - constants_in);
988 const bool many_constants = (constants_in >= 2);
989 if (!has_duplicate && !many_constants)
return false;
992 std::vector<std::pair<gate_t, double>> kept;
993 kept.reserve(coeffs.size());
994 for (
const auto &p : coeffs) {
995 if (p.second != 0.0) kept.push_back(p);
1000 replace_with_value(gc, g, b_total);
1018 if (kept.size() == 1 && b_total == 0.0) {
1019 const auto &only = kept.front();
1020 if (only.second == 1.0) {
1024 double_to_text(only.second));
1032 std::vector<gate_t> new_wires;
1033 new_wires.reserve(kept.size() + 1);
1034 for (
const auto &p : kept) {
1035 if (p.second == 1.0) {
1036 new_wires.push_back(p.first);
1041 new_wires.push_back(tm);
1044 if (b_total != 0.0) {
1048 gc.
setWires(g, std::move(new_wires));
1055 apply_rules(gc, w, include_scalar_fold);
1070unsigned apply_rules(GenericCircuit &gc,
gate_t g,
1071 bool include_scalar_fold)
1078 for (
unsigned iter = 0; iter < 32; ++iter) {
1083 double c = try_eval_constant(gc, g);
1084 if (!std::isnan(c)) {
1085 replace_with_value(gc, g, c);
1105 const auto &wires_in = gc.
getWires(g);
1106 if (wires_in.size() == 2) {
1107 const gate_t a = wires_in[0];
1108 const gate_t b = wires_in[1];
1130 const auto &wires_in = gc.
getWires(g);
1131 if (wires_in.size() == 2) {
1132 const double c = try_eval_constant(gc, wires_in[1]);
1133 if (!std::isnan(c) && c != 0.0) {
1134 const gate_t x = wires_in[0];
1136 double_to_text(1.0 / c));
1147 if (try_identity_drop(gc, g)) {
1158 if (try_mixture_lift(gc, g, include_scalar_fold)) {
1171 if (try_plus_aggregate(gc, g, include_scalar_fold)) {
1188 if (try_times_scalar_rv(gc, g)) {
1206 if (try_normal_closure(gc, g)) { ++local;
break; }
1207 if (try_erlang_closure(gc, g)) { ++local;
break; }
1208 if (try_uniform_closure(gc, g)) { ++local;
break; }
1225void simplify(GenericCircuit &gc,
gate_t g,
1226 std::unordered_set<gate_t> &done,
unsigned &counter,
1227 bool include_scalar_fold)
1233 std::stack<std::pair<gate_t, std::size_t>> stk;
1234 if (!done.insert(g).second)
return;
1237 while (!stk.empty()) {
1238 auto &frame = stk.top();
1239 gate_t cur = frame.first;
1240 const auto &wires = gc.
getWires(cur);
1241 if (frame.second < wires.size()) {
1242 gate_t child = wires[frame.second++];
1243 if (done.insert(child).second) stk.emplace(child, 0);
1248 counter += apply_rules(gc, cur, include_scalar_fold);
1257 unsigned counter = 0;
1265 for (std::size_t i = 0; i < nb; ++i) {
1266 auto g =
static_cast<gate_t>(i);
1268 double c = try_eval_constant(gc, g);
1269 if (!std::isnan(c)) {
1270 replace_with_value(gc, g, c);
1279 unsigned counter = 0;
1291 std::unordered_set<gate_t> done;
1293 for (std::size_t i = 0; i < nb; ++i) {
1294 simplify(gc,
static_cast<gate_t>(i), done, counter,
1313 for (std::size_t i = 0; i < nb; ++i) {
1314 auto g =
static_cast<gate_t>(i);
1316 if (try_times_scalar_rv(gc, g)) ++counter;
1317 else if (try_neg_rv(gc, g)) ++counter;
1338 const auto &wires = gc.
getWires(cmp_gate);
1339 if (wires.size() != 2)
return false;
1341 std::unordered_set<gate_t> seen;
1342 std::stack<gate_t> stk;
1345 while (!stk.empty()) {
1346 gate_t g = stk.top(); stk.pop();
1347 if (!seen.insert(g).second)
continue;
1366 if (mw.size() != 3)
return false;
1384void collect_cmp_rv_footprint(
const GenericCircuit &gc,
gate_t cmp_gate,
1385 std::unordered_set<gate_t> &fp)
1387 std::unordered_set<gate_t> seen;
1388 std::stack<gate_t> stk;
1390 while (!stk.empty()) {
1391 gate_t g = stk.top(); stk.pop();
1392 if (!seen.insert(g).second)
continue;
1394 if (t ==
gate_rv) { fp.insert(g);
continue; }
1412 if (mw.size() == 3) { stk.push(mw[1]); stk.push(mw[2]); }
1434constexpr std::size_t JOINT_TABLE_K_MAX = 8;
1448bool is_analytic_singleton_cmp(
const GenericCircuit &gc,
gate_t cmp_gate)
1450 const auto &wires = gc.
getWires(cmp_gate);
1451 if (wires.size() != 2)
return false;
1490struct FastPathInfo {
1492 std::vector<ComparisonOperator> ops;
1493 std::vector<double> thresholds;
1539std::optional<FastPathInfo>
1540detect_shared_scalar(
const GenericCircuit &gc,
1541 const std::vector<gate_t> &cmps)
1544 info.ops.reserve(cmps.size());
1545 info.thresholds.reserve(cmps.size());
1549 const auto &wires = gc.
getWires(c);
1550 if (wires.size() != 2)
return std::nullopt;
1554 if (!ok)
return std::nullopt;
1559 return std::nullopt;
1562 double threshold = std::numeric_limits<double>::quiet_NaN();
1565 scalar_side = wires[0];
1567 catch (
const CircuitException &) {
return std::nullopt; }
1569 scalar_side = wires[1];
1571 catch (
const CircuitException &) {
return std::nullopt; }
1572 effective_op = flip_cmp_op(op);
1574 return std::nullopt;
1578 info.scalar = scalar_side;
1580 }
else if (info.scalar != scalar_side) {
1581 return std::nullopt;
1583 info.ops.push_back(effective_op);
1584 info.thresholds.push_back(threshold);
1608void inline_fast_path(GenericCircuit &gc,
1609 const std::vector<gate_t> &cmps,
1610 const FastPathInfo &info,
1616 std::vector<double> ts = info.thresholds;
1617 std::sort(ts.begin(), ts.end());
1618 ts.erase(std::unique(ts.begin(), ts.end()), ts.end());
1619 const std::size_t m = ts.size();
1620 const std::size_t nb_intervals = m + 1;
1634 std::vector<double> interval_probs(nb_intervals, 0.0);
1635 bool analytical =
false;
1639 std::vector<double> cdf_at_boundary(m);
1641 for (std::size_t i = 0; i < m; ++i) {
1642 cdf_at_boundary[i] =
cdfAt(*spec, ts[i]);
1643 if (std::isnan(cdf_at_boundary[i])) { all_ok =
false;
break; }
1646 interval_probs[0] = cdf_at_boundary[0];
1647 for (std::size_t i = 1; i < m; ++i)
1648 interval_probs[i] = cdf_at_boundary[i] - cdf_at_boundary[i - 1];
1649 interval_probs[m] = 1.0 - cdf_at_boundary[m - 1];
1656 for (
double s : draws) {
1657 auto it = std::upper_bound(ts.begin(), ts.end(), s);
1658 std::size_t idx =
static_cast<std::size_t
>(it - ts.begin());
1659 ++interval_probs[idx];
1661 for (
auto &p : interval_probs) p /= samples;
1670 std::vector<unsigned long> outcome_word(nb_intervals, 0);
1671 for (std::size_t i = 0; i < nb_intervals; ++i) {
1673 if (i == 0) point = ts[0] - 1.0;
1674 else if (i == m) point = ts[m - 1] + 1.0;
1675 else point = 0.5 * (ts[i - 1] + ts[i]);
1676 unsigned long w = 0;
1677 for (std::size_t j = 0; j < info.thresholds.size(); ++j) {
1678 if (apply_cmp(point, info.ops[j], info.thresholds[j]))
1681 outcome_word[i] = w;
1687 std::vector<gate_t> mul_for_interval(nb_intervals,
1688 static_cast<gate_t>(-1));
1689 for (std::size_t i = 0; i < nb_intervals; ++i) {
1690 if (interval_probs[i] <= 0.0)
continue;
1691 mul_for_interval[i] =
1693 static_cast<unsigned>(i));
1698 for (std::size_t j = 0; j < cmps.size(); ++j) {
1699 std::vector<gate_t> plus_wires;
1700 plus_wires.reserve(nb_intervals);
1701 for (std::size_t i = 0; i < nb_intervals; ++i) {
1702 if (!(outcome_word[i] & (1ul << j)))
continue;
1703 gate_t mw = mul_for_interval[i];
1704 if (mw ==
static_cast<gate_t>(-1))
continue;
1705 plus_wires.push_back(mw);
1731void inline_joint_table(GenericCircuit &gc,
1732 const std::vector<gate_t> &cmps,
1735 const unsigned k =
static_cast<unsigned>(cmps.size());
1750 const std::size_t nb_outcomes = std::size_t{1} << k;
1751 std::vector<gate_t> mul_for_outcome(nb_outcomes,
1752 static_cast<gate_t>(-1));
1753 for (std::size_t w = 0; w < nb_outcomes; ++w) {
1754 if (probs[w] <= 0.0)
continue;
1755 mul_for_outcome[w] =
1757 static_cast<unsigned>(w));
1762 for (
unsigned i = 0; i < k; ++i) {
1763 std::vector<gate_t> plus_wires;
1764 plus_wires.reserve(nb_outcomes / 2);
1765 for (std::size_t w = 0; w < nb_outcomes; ++w) {
1766 if ((w & (std::size_t{1} << i)) == 0)
continue;
1767 gate_t m = mul_for_outcome[w];
1768 if (m ==
static_cast<gate_t>(-1))
continue;
1769 plus_wires.push_back(m);
1779 if (samples == 0)
return 0;
1788 std::vector<gate_t> cmps;
1789 for (std::size_t i = 0; i < nb; ++i) {
1790 auto g =
static_cast<gate_t>(i);
1797 std::unordered_map<gate_t, std::unordered_set<gate_t>> footprints;
1798 footprints.reserve(cmps.size());
1800 collect_cmp_rv_footprint(gc, c, footprints[c]);
1807 std::vector<std::size_t> parent(cmps.size());
1808 for (std::size_t i = 0; i < cmps.size(); ++i) parent[i] = i;
1809 auto find = [&](std::size_t x) {
1810 while (parent[x] != x) {
1811 parent[x] = parent[parent[x]];
1816 auto unite = [&](std::size_t a, std::size_t b) {
1817 a = find(a); b = find(b);
1818 if (a != b) parent[a] = b;
1820 for (std::size_t i = 0; i < cmps.size(); ++i) {
1821 for (std::size_t j = i + 1; j < cmps.size(); ++j) {
1822 if (find(i) == find(j))
continue;
1823 const auto &fp_i = footprints[cmps[i]];
1824 const auto &fp_j = footprints[cmps[j]];
1825 const auto &small = fp_i.size() < fp_j.size() ? fp_i : fp_j;
1826 const auto &big = fp_i.size() < fp_j.size() ? fp_j : fp_i;
1827 for (
gate_t rv : small) {
1828 if (big.count(rv)) { unite(i, j);
break; }
1834 std::unordered_map<std::size_t, std::vector<gate_t>> groups;
1835 for (std::size_t i = 0; i < cmps.size(); ++i)
1836 groups[find(i)].push_back(cmps[i]);
1838 unsigned resolved = 0;
1839 for (
auto &[root, group] : groups) {
1844 bool all_pristine =
true;
1848 if (!all_pristine)
continue;
1850 if (group.size() == 1) {
1857 if (is_analytic_singleton_cmp(gc, group[0]))
continue;
1874 if (
auto info = detect_shared_scalar(gc, group)) {
1875 inline_fast_path(gc, group, *info, samples);
1876 resolved +=
static_cast<unsigned>(group.size());
1880 if (group.size() > JOINT_TABLE_K_MAX)
continue;
1882 inline_joint_table(gc, group, samples);
1883 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)