14#include <unordered_map>
15#include <unordered_set>
35constexpr double NaN = std::numeric_limits<double>::quiet_NaN();
58const std::unordered_set<gate_t> *g_shared_base_rvs =
nullptr;
60inline bool is_shared_base_rv(
gate_t rv)
62 return g_shared_base_rvs !=
nullptr && g_shared_base_rvs->count(rv) != 0;
70void collect_reachable_base_rvs(
const GenericCircuit &gc,
gate_t start,
71 std::unordered_set<gate_t> &out)
73 std::unordered_set<gate_t> seen;
74 std::stack<gate_t> stk;
76 while (!stk.empty()) {
77 gate_t g = stk.top(); stk.pop();
78 if (!seen.insert(g).second)
continue;
91 if (mw.size() == 3) { stk.push(mw[1]); stk.push(mw[2]); }
111double try_eval_constant(
const GenericCircuit &gc,
gate_t g)
116 catch (
const CircuitException &) {
return NaN; }
122 if (wires.empty())
return NaN;
124 double first = try_eval_constant(gc, wires[0]);
125 if (std::isnan(first))
return NaN;
130 for (std::size_t i = 1; i < wires.size(); ++i) {
131 double v = try_eval_constant(gc, wires[i]);
132 if (std::isnan(v))
return NaN;
139 for (std::size_t i = 1; i < wires.size(); ++i) {
140 double v = try_eval_constant(gc, wires[i]);
141 if (std::isnan(v))
return NaN;
147 if (wires.size() != 2)
return NaN;
148 double v = try_eval_constant(gc, wires[1]);
149 if (std::isnan(v))
return NaN;
153 if (wires.size() != 2)
return NaN;
154 double v = try_eval_constant(gc, wires[1]);
155 if (std::isnan(v))
return NaN;
159 if (wires.size() != 1)
return NaN;
163 for (std::size_t i = 1; i < wires.size(); ++i) {
164 double v = try_eval_constant(gc, wires[i]);
165 if (std::isnan(v))
return NaN;
172 for (std::size_t i = 1; i < wires.size(); ++i) {
173 double v = try_eval_constant(gc, wires[i]);
174 if (std::isnan(v))
return NaN;
180 if (wires.size() != 2)
return NaN;
181 double e = try_eval_constant(gc, wires[1]);
182 if (std::isnan(e))
return NaN;
188 return std::pow(first, e);
191 if (wires.size() != 1)
return NaN;
193 return std::log(first);
195 if (wires.size() != 1)
return NaN;
196 return std::exp(first);
217bool subtree_contains_agg(
const GenericCircuit &gc,
gate_t g)
219 std::unordered_set<gate_t> seen;
220 std::stack<gate_t> stk;
222 while (!stk.empty()) {
223 gate_t cur = stk.top(); stk.pop();
224 if (!seen.insert(cur).second)
continue;
238void replace_with_value(GenericCircuit &gc,
gate_t g,
double c)
247bool is_value_equal_to(
const GenericCircuit &gc,
gate_t g,
double target)
251 catch (
const CircuitException &) {
return false; }
270bool try_identity_drop(GenericCircuit &gc,
gate_t g)
276 std::vector<gate_t> kept;
277 kept.reserve(wires.size());
279 if (!is_value_equal_to(gc, w, 0.0)) kept.push_back(w);
281 if (kept.size() == wires.size())
return false;
283 replace_with_value(gc, g, 0.0);
286 wires = std::move(kept);
292 if (is_value_equal_to(gc, w, 0.0)) {
293 replace_with_value(gc, g, 0.0);
297 std::vector<gate_t> kept;
298 kept.reserve(wires.size());
300 if (!is_value_equal_to(gc, w, 1.0)) kept.push_back(w);
302 if (kept.size() == wires.size())
return false;
304 replace_with_value(gc, g, 1.0);
307 wires = std::move(kept);
333bool is_invalid(
gate_t g) {
return g == INVALID_GATE; }
360std::optional<LinearTerm>
361decompose_linear_term(
const GenericCircuit &gc,
gate_t g)
368 catch (
const CircuitException &) {
return std::nullopt; }
369 return LinearTerm{INVALID_GATE, 0.0, v};
376 return LinearTerm{g, 1.0, 0.0};
391 return LinearTerm{g, 1.0, 0.0};
407 && wires.size() == 1) {
408 return decompose_linear_term(gc, wires[0]);
412 if (wires.size() != 1)
return std::nullopt;
413 auto inner = decompose_linear_term(gc, wires[0]);
414 if (!inner)
return std::nullopt;
415 return LinearTerm{inner->rv_gate, -inner->a, -inner->b};
419 if (wires.size() != 2)
return std::nullopt;
422 gate_t var_side = INVALID_GATE;
425 catch (
const CircuitException &) {
return std::nullopt; }
429 catch (
const CircuitException &) {
return std::nullopt; }
434 auto inner = decompose_linear_term(gc, var_side);
435 if (!inner)
return std::nullopt;
436 return LinearTerm{inner->rv_gate, c * inner->a, c * inner->b};
476bool try_sum_closure(GenericCircuit &gc,
gate_t g)
479 if (wires.size() < 2)
return false;
481 std::vector<LinearTerm> lterms;
482 lterms.reserve(wires.size());
484 auto term = decompose_linear_term(gc, w);
485 if (!term)
return false;
486 lterms.push_back(*term);
490 std::vector<std::unique_ptr<Distribution>> dists(lterms.size());
491 std::vector<ClosureTerm> terms;
492 terms.reserve(lterms.size());
493 std::unordered_set<gate_t> seen_rvs;
494 for (std::size_t i = 0; i < lterms.size(); ++i) {
495 const auto &t = lterms[i];
496 if (is_invalid(t.rv_gate)) {
497 terms.push_back({
nullptr, t.a, t.b});
500 if (!seen_rvs.insert(t.rv_gate).second)
return false;
504 if (is_shared_base_rv(t.rv_gate))
return false;
506 if (!spec)
return false;
508 terms.push_back({dists[i].get(), t.a, t.b});
512 if (!folded)
return false;
530bool try_product_closure(GenericCircuit &gc,
gate_t g)
533 if (wires.size() < 2)
return false;
535 double c_total = 1.0;
536 std::vector<std::unique_ptr<Distribution>> dists;
537 std::vector<const Distribution *> factors;
538 std::unordered_set<gate_t> seen_rvs;
543 catch (
const CircuitException &) {
return false; }
546 if (t !=
gate_rv)
return false;
547 if (!seen_rvs.insert(w).second)
return false;
548 if (is_shared_base_rv(w))
return false;
550 if (!spec)
return false;
552 factors.push_back(dists.back().get());
554 if (factors.size() < 2)
return false;
557 if (!combined)
return false;
558 if (c_total != 1.0) {
559 combined = combined->scale(c_total);
560 if (!combined)
return false;
577bool try_transform_closure(GenericCircuit &gc,
gate_t g)
583 if (!transform)
return false;
585 if (wires.size() != 1)
return false;
587 if (is_shared_base_rv(wires[0]))
return false;
589 if (!spec)
return false;
592 if (!image)
return false;
613bool try_neg_rv(GenericCircuit &gc,
gate_t g)
619 if (wires.size() != 1)
return false;
621 if (is_shared_base_rv(wires[0]))
return false;
624 if (!spec)
return false;
627 if (!negated)
return false;
663unsigned apply_rules(GenericCircuit &gc,
gate_t g,
664 bool include_scalar_fold);
683bool try_categorical_mixture_lift(GenericCircuit &gc,
gate_t g,
686 const std::vector<gate_t> &others)
699 catch (
const CircuitException &) {
return false; }
713 const std::vector<gate_t> mw = gc.
getWires(mix_gate);
715 std::vector<gate_t> new_wires;
716 new_wires.reserve(mw.size());
717 new_wires.push_back(key);
718 for (std::size_t i = 1; i < mw.size(); ++i) {
719 const gate_t old_mul = mw[i];
722 catch (
const CircuitException &) {
return false; }
726 const double p = gc.
getProb(old_mul);
727 const auto vi =
static_cast<unsigned>(gc.
getInfos(old_mul).first);
730 new_wires.push_back(new_mul);
736bool try_mixture_lift(GenericCircuit &gc,
gate_t g,
737 bool include_scalar_fold)
743 if (wires.size() < 2)
return false;
746 std::size_t mix_idx =
static_cast<std::size_t
>(-1);
747 for (std::size_t i = 0; i < wires.size(); ++i) {
749 if (mix_idx !=
static_cast<std::size_t
>(-1))
return false;
753 if (mix_idx ==
static_cast<std::size_t
>(-1))
return false;
755 const auto mix_gate = wires[mix_idx];
760 std::vector<gate_t> others;
761 others.reserve(wires.size() - 1);
762 for (std::size_t i = 0; i < wires.size(); ++i) {
763 if (i != mix_idx) others.push_back(wires[i]);
770 return try_categorical_mixture_lift(gc, g, op, mix_gate, others);
774 const auto &mw = gc.
getWires(mix_gate);
775 if (mw.size() != 3)
return false;
776 const gate_t p_tok = mw[0];
777 const gate_t x_tok = mw[1];
778 const gate_t y_tok = mw[2];
784 std::vector<gate_t> new_x_wires = others; new_x_wires.push_back(x_tok);
785 std::vector<gate_t> new_y_wires = others; new_y_wires.push_back(y_tok);
801 apply_rules(gc, new_x, include_scalar_fold);
802 apply_rules(gc, new_y, include_scalar_fold);
832bool try_times_scalar_rv(GenericCircuit &gc,
gate_t g)
837 if (wires.size() != 2)
return false;
841 gate_t rv_side = INVALID_GATE;
845 catch (
const CircuitException &) {
return false; }
850 catch (
const CircuitException &) {
return false; }
858 if (c == 0.0 || c == 1.0)
return false;
860 if (is_shared_base_rv(rv_side))
return false;
863 if (!spec)
return false;
866 if (!scaled)
return false;
872 if (
auto dirac = scaled->asDirac()) {
873 replace_with_value(gc, g, *dirac);
910bool try_plus_aggregate(GenericCircuit &gc,
gate_t g,
911 bool include_scalar_fold)
915 const auto &wires_in = gc.
getWires(g);
916 if (wires_in.size() < 2)
return false;
918 std::vector<LinearTerm> terms;
919 terms.reserve(wires_in.size());
920 for (
gate_t w : wires_in) {
921 auto t = decompose_linear_term(gc, w);
922 if (!t)
return false;
929 std::vector<std::pair<gate_t, double>> coeffs;
930 double b_total = 0.0;
931 unsigned constants_in = 0;
932 for (
const auto &t : terms) {
934 if (is_invalid(t.rv_gate)) {
939 for (
auto &p : coeffs) {
940 if (p.first == t.rv_gate) {
946 if (!found) coeffs.emplace_back(t.rv_gate, t.a);
953 const bool has_duplicate = (coeffs.size() < terms.size() - constants_in);
954 const bool many_constants = (constants_in >= 2);
955 if (!has_duplicate && !many_constants)
return false;
958 std::vector<std::pair<gate_t, double>> kept;
959 kept.reserve(coeffs.size());
960 for (
const auto &p : coeffs) {
961 if (p.second != 0.0) kept.push_back(p);
966 replace_with_value(gc, g, b_total);
984 if (kept.size() == 1 && b_total == 0.0) {
985 const auto &only = kept.front();
986 if (only.second == 1.0) {
998 std::vector<gate_t> new_wires;
999 new_wires.reserve(kept.size() + 1);
1000 for (
const auto &p : kept) {
1001 if (p.second == 1.0) {
1002 new_wires.push_back(p.first);
1007 new_wires.push_back(tm);
1010 if (b_total != 0.0) {
1014 gc.
setWires(g, std::move(new_wires));
1021 apply_rules(gc, w, include_scalar_fold);
1036unsigned apply_rules(GenericCircuit &gc,
gate_t g,
1037 bool include_scalar_fold)
1044 for (
unsigned iter = 0; iter < 32; ++iter) {
1049 double c = try_eval_constant(gc, g);
1050 if (!std::isnan(c)) {
1051 replace_with_value(gc, g, c);
1071 const auto &wires_in = gc.
getWires(g);
1072 if (wires_in.size() == 2) {
1073 const gate_t a = wires_in[0];
1074 const gate_t b = wires_in[1];
1100 const auto &wires_in = gc.
getWires(g);
1101 if (wires_in.size() == 2 && !subtree_contains_agg(gc, wires_in[0])) {
1102 const double c = try_eval_constant(gc, wires_in[1]);
1103 if (!std::isnan(c) && c != 0.0) {
1104 const gate_t x = wires_in[0];
1117 if (try_identity_drop(gc, g)) {
1128 if (try_mixture_lift(gc, g, include_scalar_fold)) {
1141 if (try_plus_aggregate(gc, g, include_scalar_fold)) {
1158 if (try_times_scalar_rv(gc, g)) {
1171 if (try_sum_closure(gc, g)) { ++local;
break; }
1174 if (try_product_closure(gc, g)) { ++local;
break; }
1177 if (try_transform_closure(gc, g)) { ++local;
break; }
1194void simplify(GenericCircuit &gc,
gate_t g,
1195 std::unordered_set<gate_t> &done,
unsigned &counter,
1196 bool include_scalar_fold)
1202 std::stack<std::pair<gate_t, std::size_t>> stk;
1203 if (!done.insert(g).second)
return;
1206 while (!stk.empty()) {
1207 auto &frame = stk.top();
1208 gate_t cur = frame.first;
1209 const auto &wires = gc.
getWires(cur);
1210 if (frame.second < wires.size()) {
1211 gate_t child = wires[frame.second++];
1212 if (done.insert(child).second) stk.emplace(child, 0);
1217 counter += apply_rules(gc, cur, include_scalar_fold);
1226 unsigned counter = 0;
1234 for (std::size_t i = 0; i < nb; ++i) {
1235 auto g =
static_cast<gate_t>(i);
1237 double c = try_eval_constant(gc, g);
1238 if (!std::isnan(c)) {
1239 replace_with_value(gc, g, c);
1248 unsigned counter = 0;
1250 for (std::size_t i = 0; i < nb; ++i) {
1251 auto g =
static_cast<gate_t>(i);
1257 const auto &wires = gc.
getWires(g);
1258 if (wires.size() != 3)
continue;
1271 const gate_t sel = wires[0];
1293 }
else if (pi == 0.0) {
1303 unsigned counter = 0;
1324 std::unordered_set<gate_t> shared_base_rvs;
1328 std::unordered_map<gate_t, unsigned> cmp_footprint_count;
1329 for (std::size_t i = 0; i < nb; ++i) {
1330 auto g =
static_cast<gate_t>(i);
1333 std::unordered_set<gate_t> fp;
1334 collect_reachable_base_rvs(gc, w, fp);
1335 for (
gate_t rv : fp) ++cmp_footprint_count[rv];
1338 for (
const auto &[rv, n] : cmp_footprint_count)
1339 if (n > 1) shared_base_rvs.insert(rv);
1341 for (std::size_t i = 0; i < nb; ++i) {
1342 auto g =
static_cast<gate_t>(i);
1347 if (arms.size() < 2)
continue;
1348 std::unordered_map<gate_t, unsigned> arm_count;
1349 for (
gate_t arm : arms) {
1350 std::unordered_set<gate_t> fp;
1351 collect_reachable_base_rvs(gc, arm, fp);
1352 for (
gate_t rv : fp) ++arm_count[rv];
1354 for (
const auto &[rv, n] : arm_count)
1355 if (n > 1) shared_base_rvs.insert(rv);
1358 g_shared_base_rvs = &shared_base_rvs;
1359 struct SharedGuard {
1360 ~SharedGuard() { g_shared_base_rvs =
nullptr; }
1373 std::unordered_set<gate_t> done;
1375 for (std::size_t i = 0; i < nb; ++i) {
1376 simplify(gc,
static_cast<gate_t>(i), done, counter,
1395 for (std::size_t i = 0; i < nb; ++i) {
1396 auto g =
static_cast<gate_t>(i);
1398 if (try_times_scalar_rv(gc, g)) ++counter;
1399 else if (try_neg_rv(gc, g)) ++counter;
1420 const auto &wires = gc.
getWires(cmp_gate);
1421 if (wires.size() != 2)
return false;
1423 std::unordered_set<gate_t> seen;
1424 std::stack<gate_t> stk;
1427 while (!stk.empty()) {
1428 gate_t g = stk.top(); stk.pop();
1429 if (!seen.insert(g).second)
continue;
1448 if (mw.size() != 3)
return false;
1466void collect_cmp_rv_footprint(
const GenericCircuit &gc,
gate_t cmp_gate,
1467 std::unordered_set<gate_t> &fp)
1469 std::unordered_set<gate_t> seen;
1470 std::stack<gate_t> stk;
1472 while (!stk.empty()) {
1473 gate_t g = stk.top(); stk.pop();
1474 if (!seen.insert(g).second)
continue;
1504 if (mw.size() == 3) { stk.push(mw[1]); stk.push(mw[2]); }
1533void collect_cmp_mixture_selectors(
const GenericCircuit &gc,
gate_t cmp_gate,
1534 std::unordered_set<gate_t> &sels)
1536 std::unordered_set<gate_t> seen;
1537 std::stack<gate_t> stk;
1539 while (!stk.empty()) {
1540 gate_t g = stk.top(); stk.pop();
1541 if (!seen.insert(g).second)
continue;
1550 if (mw.size() == 3) {
1572constexpr std::size_t JOINT_TABLE_K_MAX = 8;
1586bool is_analytic_singleton_cmp(
const GenericCircuit &gc,
gate_t cmp_gate)
1588 const auto &wires = gc.
getWires(cmp_gate);
1589 if (wires.size() != 2)
return false;
1630struct FastPathInfo {
1632 std::vector<ComparisonOperator> ops;
1633 std::vector<double> thresholds;
1679std::optional<FastPathInfo>
1680detect_shared_scalar(
const GenericCircuit &gc,
1681 const std::vector<gate_t> &cmps)
1684 info.ops.reserve(cmps.size());
1685 info.thresholds.reserve(cmps.size());
1689 const auto &wires = gc.
getWires(c);
1690 if (wires.size() != 2)
return std::nullopt;
1694 if (!ok)
return std::nullopt;
1699 return std::nullopt;
1702 double threshold = std::numeric_limits<double>::quiet_NaN();
1705 scalar_side = wires[0];
1707 catch (
const CircuitException &) {
return std::nullopt; }
1709 scalar_side = wires[1];
1711 catch (
const CircuitException &) {
return std::nullopt; }
1712 effective_op = flip_cmp_op(op);
1714 return std::nullopt;
1718 info.scalar = scalar_side;
1720 }
else if (info.scalar != scalar_side) {
1721 return std::nullopt;
1723 info.ops.push_back(effective_op);
1724 info.thresholds.push_back(threshold);
1755bool inline_fast_path(GenericCircuit &gc,
1756 const std::vector<gate_t> &cmps,
1757 const FastPathInfo &info,
1764 std::vector<double> ts = info.thresholds;
1765 std::sort(ts.begin(), ts.end());
1766 ts.erase(std::unique(ts.begin(), ts.end()), ts.end());
1767 const std::size_t m = ts.size();
1768 const std::size_t nb_intervals = m + 1;
1782 std::vector<double> interval_probs(nb_intervals, 0.0);
1783 bool analytical =
false;
1787 std::vector<double> cdf_at_boundary(m);
1789 for (std::size_t i = 0; i < m; ++i) {
1790 cdf_at_boundary[i] =
cdfAt(*spec, ts[i]);
1791 if (std::isnan(cdf_at_boundary[i])) { all_ok =
false;
break; }
1794 interval_probs[0] = cdf_at_boundary[0];
1795 for (std::size_t i = 1; i < m; ++i)
1796 interval_probs[i] = cdf_at_boundary[i] - cdf_at_boundary[i - 1];
1797 interval_probs[m] = 1.0 - cdf_at_boundary[m - 1];
1808 if (!allow_mc)
return false;
1810 for (
double s : draws) {
1811 auto it = std::upper_bound(ts.begin(), ts.end(), s);
1812 std::size_t idx =
static_cast<std::size_t
>(it - ts.begin());
1813 ++interval_probs[idx];
1815 for (
auto &p : interval_probs) p /= samples;
1824 std::vector<unsigned long> outcome_word(nb_intervals, 0);
1825 for (std::size_t i = 0; i < nb_intervals; ++i) {
1827 if (i == 0) point = ts[0] - 1.0;
1828 else if (i == m) point = ts[m - 1] + 1.0;
1829 else point = 0.5 * (ts[i - 1] + ts[i]);
1830 unsigned long w = 0;
1831 for (std::size_t j = 0; j < info.thresholds.size(); ++j) {
1832 if (apply_cmp(point, info.ops[j], info.thresholds[j]))
1835 outcome_word[i] = w;
1841 std::vector<gate_t> mul_for_interval(nb_intervals,
1842 static_cast<gate_t>(-1));
1843 for (std::size_t i = 0; i < nb_intervals; ++i) {
1844 if (interval_probs[i] <= 0.0)
continue;
1845 mul_for_interval[i] =
1847 static_cast<unsigned>(i));
1852 for (std::size_t j = 0; j < cmps.size(); ++j) {
1853 std::vector<gate_t> plus_wires;
1854 plus_wires.reserve(nb_intervals);
1855 for (std::size_t i = 0; i < nb_intervals; ++i) {
1856 if (!(outcome_word[i] & (1ul << j)))
continue;
1857 gate_t mw = mul_for_interval[i];
1858 if (mw ==
static_cast<gate_t>(-1))
continue;
1859 plus_wires.push_back(mw);
1886void inline_joint_table(GenericCircuit &gc,
1887 const std::vector<gate_t> &cmps,
1890 const unsigned k =
static_cast<unsigned>(cmps.size());
1905 const std::size_t nb_outcomes = std::size_t{1} << k;
1906 std::vector<gate_t> mul_for_outcome(nb_outcomes,
1907 static_cast<gate_t>(-1));
1908 for (std::size_t w = 0; w < nb_outcomes; ++w) {
1909 if (probs[w] <= 0.0)
continue;
1910 mul_for_outcome[w] =
1912 static_cast<unsigned>(w));
1917 for (
unsigned i = 0; i < k; ++i) {
1918 std::vector<gate_t> plus_wires;
1919 plus_wires.reserve(nb_outcomes / 2);
1920 for (std::size_t w = 0; w < nb_outcomes; ++w) {
1921 if ((w & (std::size_t{1} << i)) == 0)
continue;
1922 gate_t m = mul_for_outcome[w];
1923 if (m ==
static_cast<gate_t>(-1))
continue;
1924 plus_wires.push_back(m);
1939struct PivotIslandInfo {
1940 DistributionSpec pivotSpec;
1943 DistributionSpec other;
1947 std::vector<Factor> factors;
1954std::optional<PivotIslandInfo>
1955detect_shared_pivot_rv(
const GenericCircuit &gc,
1956 const std::vector<gate_t> &cmps)
1959 bool havePivot =
false;
1960 std::optional<DistributionSpec> pivotSpec;
1961 PivotIslandInfo info;
1962 std::unordered_set<gate_t> othersSeen;
1967 if (w.size() != 2)
return std::nullopt;
1971 return std::nullopt;
1974 gate_t pv, other;
bool pivotLeft;
1976 (!havePivot || w[0] == pivot)) { pv = w[0]; other = w[1]; pivotLeft =
true; }
1978 (!havePivot || w[1] == pivot)) { pv = w[1]; other = w[0]; pivotLeft =
false; }
1979 else return std::nullopt;
1983 if (!sp)
return std::nullopt;
1984 pivot = pv; pivotSpec = *sp; havePivot =
true;
1991 const bool trueIsGreater = pivotLeft ? greaterOp : lessOp;
1993 PivotIslandInfo::Factor f;
1994 f.trueIsGreater = trueIsGreater;
1998 catch (
const CircuitException &) {
return std::nullopt; }
2000 if (!othersSeen.insert(other).second)
return std::nullopt;
2002 if (!sp)
return std::nullopt;
2005 }
else return std::nullopt;
2006 info.factors.push_back(std::move(f));
2008 if (!havePivot)
return std::nullopt;
2009 info.pivotSpec = *pivotSpec;
2023bool inline_analytic_pivot_joint_table(GenericCircuit &gc,
2024 const std::vector<gate_t> &cmps,
2025 const PivotIslandInfo &info)
2027 const unsigned k =
static_cast<unsigned>(cmps.size());
2028 const std::size_t nb_outcomes = std::size_t{1} << k;
2032 if (!dX->integrationRange(lo0, hi0))
return false;
2035 std::vector<std::unique_ptr<Distribution>> otherDist(k);
2036 for (
unsigned j = 0; j < k; ++j)
2037 if (!info.factors[j].isConst)
2040 std::vector<double> probs(nb_outcomes, 0.0);
2041 for (std::size_t w = 0; w < nb_outcomes; ++w) {
2043 double lo = lo0, hi = hi0;
2044 for (
unsigned j = 0; j < k; ++j) {
2045 if (!info.factors[j].isConst)
continue;
2046 const bool bit = (w >> j) & 1u;
2048 const bool greater = (info.factors[j].trueIsGreater == bit);
2049 if (greater) lo = std::max(lo, info.factors[j].konst);
2050 else hi = std::min(hi, info.factors[j].konst);
2052 if (!(hi > lo)) { probs[w] = 0.0;
continue; }
2056 const double fX = dX->pdf(x);
2057 if (std::isnan(fX))
return std::numeric_limits<double>::quiet_NaN();
2059 for (
unsigned j = 0; j < k; ++j) {
2060 if (info.factors[j].isConst)
continue;
2061 const bool bit = (w >> j) & 1u;
2062 const bool greater = (info.factors[j].trueIsGreater == bit);
2063 const double FY = otherDist[j]->cdf(x);
2064 if (std::isnan(FY))
return std::numeric_limits<double>::quiet_NaN();
2065 weight *= greater ? FY : (1.0 - FY);
2069 if (std::isnan(cell))
return false;
2076 std::vector<gate_t> mul_for_outcome(nb_outcomes,
static_cast<gate_t>(-1));
2077 for (std::size_t w = 0; w < nb_outcomes; ++w) {
2078 if (probs[w] <= 0.0)
continue;
2079 mul_for_outcome[w] =
2082 for (
unsigned i = 0; i < k; ++i) {
2083 std::vector<gate_t> plus_wires;
2084 for (std::size_t w = 0; w < nb_outcomes; ++w) {
2085 if ((w & (std::size_t{1} << i)) == 0)
continue;
2086 gate_t mw = mul_for_outcome[w];
2087 if (mw ==
static_cast<gate_t>(-1))
continue;
2088 plus_wires.push_back(mw);
2110 const bool allow_mc = (samples > 0);
2119 std::vector<gate_t> cmps;
2120 for (std::size_t i = 0; i < nb; ++i) {
2121 auto g =
static_cast<gate_t>(i);
2128 std::unordered_map<gate_t, std::unordered_set<gate_t>> footprints;
2129 footprints.reserve(cmps.size());
2131 collect_cmp_rv_footprint(gc, c, footprints[c]);
2139 std::unordered_map<gate_t, unsigned> indeg;
2141 for (std::size_t i = 0; i < nb; ++i)
2150 std::vector<std::size_t> parent(cmps.size());
2151 for (std::size_t i = 0; i < cmps.size(); ++i) parent[i] = i;
2152 auto find = [&](std::size_t x) {
2153 while (parent[x] != x) {
2154 parent[x] = parent[parent[x]];
2159 auto unite = [&](std::size_t a, std::size_t b) {
2160 a = find(a); b = find(b);
2161 if (a != b) parent[a] = b;
2163 for (std::size_t i = 0; i < cmps.size(); ++i) {
2164 for (std::size_t j = i + 1; j < cmps.size(); ++j) {
2165 if (find(i) == find(j))
continue;
2166 const auto &fp_i = footprints[cmps[i]];
2167 const auto &fp_j = footprints[cmps[j]];
2168 const auto &small = fp_i.size() < fp_j.size() ? fp_i : fp_j;
2169 const auto &big = fp_i.size() < fp_j.size() ? fp_j : fp_i;
2170 for (
gate_t rv : small) {
2171 if (big.count(rv)) { unite(i, j);
break; }
2177 std::unordered_map<std::size_t, std::vector<gate_t>> groups;
2178 for (std::size_t i = 0; i < cmps.size(); ++i)
2179 groups[find(i)].push_back(cmps[i]);
2181 unsigned resolved = 0;
2182 for (
auto &[root, group] : groups) {
2187 bool all_pristine =
true;
2191 if (!all_pristine)
continue;
2210 std::unordered_set<gate_t> island;
2211 std::unordered_map<gate_t, unsigned> island_ref;
2213 std::stack<gate_t> stk;
2214 for (
gate_t c : group) stk.push(c);
2215 while (!stk.empty()) {
2216 gate_t g = stk.top(); stk.pop();
2217 if (!island.insert(g).second)
continue;
2218 for (
gate_t w : gc.
getWires(g)) { ++island_ref[w]; stk.push(w); }
2224 auto sub_dag_escapes_island = [&](
gate_t s) {
2225 std::unordered_set<gate_t> seen;
2226 std::stack<gate_t> st; st.push(s);
2227 while (!st.empty()) {
2228 gate_t g = st.top(); st.pop();
2229 if (!seen.insert(g).second)
continue;
2230 unsigned inside = island_ref.count(g) ? island_ref[g] : 0;
2231 unsigned total = indeg.count(g) ? indeg[g] : 0;
2232 if (total > inside)
return true;
2237 std::unordered_set<gate_t> sels;
2238 for (
gate_t c : group) collect_cmp_mixture_selectors(gc, c, sels);
2239 bool group_couples_selector =
false;
2241 if (sub_dag_escapes_island(s)) { group_couples_selector =
true;
break; }
2242 if (group_couples_selector)
continue;
2245 if (group.size() == 1) {
2252 if (is_analytic_singleton_cmp(gc, group[0]))
continue;
2256 if (!allow_mc)
continue;
2273 if (
auto info = detect_shared_scalar(gc, group)) {
2274 if (inline_fast_path(gc, group, *info, samples, allow_mc)) {
2275 resolved +=
static_cast<unsigned>(group.size());
2281 "the joint probability of correlated comparison events over a "
2282 "composite quantity needs Monte Carlo, but provsql.rv_mc_samples "
2283 "= 0 disables it; set provsql.rv_mc_samples > 0 (comparisons "
2284 "against constants on a single distribution stay analytical)");
2293 if (
auto pinfo = detect_shared_pivot_rv(gc, group)) {
2294 if (inline_analytic_pivot_joint_table(gc, group, *pinfo)) {
2295 resolved +=
static_cast<unsigned>(group.size());
2308 "the joint probability of correlated comparison events needs "
2309 "Monte Carlo, but provsql.rv_mc_samples = 0 disables it; set "
2310 "provsql.rv_mc_samples > 0 (comparisons against constants on a "
2311 "single distribution stay analytical)");
2313 if (group.size() > JOINT_TABLE_K_MAX)
continue;
2315 inline_joint_table(gc, group, samples);
2316 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.
Per-family polymorphic view over a continuous gate_rv distribution (§F.1 class hierarchy).
Analytical expectation / variance / moment evaluator over RV circuits.
Peephole simplifier for continuous gate_arith sub-circuits.
Monte Carlo sampling over a GenericCircuit, RV-aware.
Shared 1-D quadrature core for the pivot-conjunction and order-statistic closed forms.
Continuous random-variable helpers (distribution parsing, moments).
Exception type thrown by circuit operations on invalid input.
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.
void liftConditionedToTarget(gate_t g, gate_t target)
Replace a gate_conditioned g by a transparent passthrough to its target child (a single-child gate_ar...
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.
unsigned runConstantFold(GenericCircuit &gc)
Constant-fold pass over every gate_arith in gc.
std::unique_ptr< Distribution > closeTransform(const char *transform, const Distribution &x)
The image distribution of transform applied to x, when a registered rule covers x's family; nullptr o...
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.
std::unique_ptr< Distribution > makeDistribution(const DistributionSpec &spec)
Construct the per-family Distribution for a parsed spec.
std::unique_ptr< Distribution > closePlusTerms(const std::vector< ClosureTerm > &terms)
Fold PLUS(terms) into a single distribution when a registered closure covers every family in the sum.
double simpsonIntegrate(double lo, double hi, int N, F &&f)
Composite-Simpson with N panels.
unsigned runHybridSimplifier(GenericCircuit &gc)
Run the peephole simplifier over gc.
std::unique_ptr< Distribution > closeProductFactors(const std::vector< const Distribution * > &factors)
Fold a product of independent factors into a single distribution when a registered closure covers eve...
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.
constexpr int kSimpsonPanels
Panel count shared by every composite-Simpson quadrature over a distribution's integration range: exa...
unsigned foldDegenerateMixtures(GenericCircuit &gc)
Collapse degenerate Bernoulli gate_mixture gates whose selector is certainly true (pi = 1) or certain...
double cdfAt(const DistributionSpec &d, double c)
Closed-form CDF for a basic continuous distribution.
std::string double_to_text(double v)
Format a double back into the canonical text form used by gate_value extras and gate_rv 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_PERCENTILE
continuous percentile (order-statistic aggregate): wires are interleaved [ind_1, x_1,...
@ PROVSQL_ARITH_DIV
binary, child0 / child1
@ PROVSQL_ARITH_LN
unary, natural logarithm of child0 (a negative draw raises at evaluation)
@ PROVSQL_ARITH_PLUS
n-ary, sum of children
@ PROVSQL_ARITH_POW
binary, child0 ^ child1 (real branch only: a negative base drawn with a non-integer exponent raises a...
@ PROVSQL_ARITH_NEG
unary, -child0
@ PROVSQL_ARITH_MINUS
binary, child0 - child1
@ PROVSQL_ARITH_EXP
unary, e^child0
@ PROVSQL_ARITH_TIMES
n-ary, product of children
@ PROVSQL_ARITH_MIN
n-ary, min of children (order statistic; least / min aggregate)
@ PROVSQL_ARITH_MAX
n-ary, max of children (order statistic; greatest / max aggregate)
@ 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)