ProvSQL C/C++ API
Adding support for provenance and uncertainty management to PostgreSQL databases
Loading...
Searching...
No Matches
RangeCheck.h
Go to the documentation of this file.
1/**
2 * @file RangeCheck.h
3 * @brief Support-based bound check for continuous-RV comparators.
4 *
5 * Each continuous distribution has a known support: @c uniform(a,b)
6 * on @c [a,b], @c exponential(λ) on @c [0,∞), @c normal on @c ℝ.
7 * Interval arithmetic propagates these supports through @c gate_arith
8 * combinators, giving a per-gate @c [lo,hi] bound on the value the
9 * gate can take in any possible world. When a @c gate_cmp's two
10 * sides have provably-disjoint supports, the comparator's probability
11 * is 0 or 1 exactly – no sampling required.
12 *
13 * The peephole pass walks every @c gate_cmp reachable from a root,
14 * decides probabilities where it can, and replaces decided
15 * comparators by Bernoulli @c gate_input gates via
16 * @c GenericCircuit::resolveCmpToBernoulli. Anything it cannot
17 * decide is left untouched for downstream evaluators (analytic CDFs
18 * where they apply, Monte Carlo otherwise).
19 *
20 * No external dependency.
21 */
22#ifndef PROVSQL_RANGE_CHECK_H
23#define PROVSQL_RANGE_CHECK_H
24
25#include <memory>
26#include <optional>
27#include <tuple>
28#include <utility>
29#include <variant>
30#include <vector>
31
32#include "GenericCircuit.h"
33#include "RandomVariable.h"
34
35namespace provsql {
36
37/**
38 * @brief Compute the @c [lo, hi] support interval of a scalar
39 * sub-circuit rooted at @p root.
40 *
41 * Same interval-arithmetic propagation @c runRangeCheck uses
42 * internally, exposed for the SQL @c support() function:
43 * - @c gate_value: point @c [c, c].
44 * - @c gate_rv: distribution support (uniform exact, exponential
45 * on @c [0, +∞), normal on @c (-∞, +∞)).
46 * - @c gate_arith: propagated through @c +, @c −, @c ×, @c /, unary
47 * @c −.
48 *
49 * Anything else collapses to the conservative all-real interval
50 * @c (-∞, +∞). Never throws on unrecognised gates -- callers receive
51 * the wide interval instead, which is the right semantic for "we
52 * cannot prove a tighter bound".
53 *
54 * When @p event_root is set, the returned interval is the
55 * intersection of the unconditional support with the per-RV
56 * constraints implied by the AND-conjunct chain rooted at the event
57 * (`rv op c` cmps over @p root collected via the same walker
58 * @c runRangeCheck uses for joint feasibility). Constraints we
59 * cannot interpret are silently skipped: the result is then a
60 * conservative superset of the true conditional support, never a
61 * subset.
62 */
63std::pair<double, double>
64compute_support(const GenericCircuit &gc, gate_t root,
65 std::optional<gate_t> event_root = std::nullopt);
66
67/**
68 * @brief Walk @p event_root collecting `rv op c` constraints on @p target_rv.
69 *
70 * Descends through AND-conjunct factors (@c gate_times, @c gate_one,
71 * Boolean leaves whose footprint doesn't include @p target_rv -- these
72 * are independent of the RV and contribute no truncation) collecting
73 * every @c gate_cmp interpretable as `target_rv op c` for a constant
74 * @c c, and intersects them into a running interval seeded with the
75 * unconditional support of @p target_rv.
76 *
77 * Returns the resulting interval as @c (lo, hi), or @c std::nullopt
78 * if the walk found a structure that defeats the recognisers (a
79 * @c gate_plus / @c gate_monus disjunction over the chain, a cmp
80 * shape other than `rv op const`, ...). Callers treat
81 * @c std::nullopt as "fall back to the unconditional case" --
82 * sound for support and MC fallback for moments.
83 *
84 * Constraints on RVs other than @p target_rv are ignored; they affect
85 * @c P(A) but not the truncation of the target's distribution.
86 */
87std::optional<std::pair<double, double>>
88collectRvConstraints(const GenericCircuit &gc, gate_t event_root,
89 gate_t target_rv);
90
91/**
92 * @brief Detection result for a closed-form, optionally-truncated
93 * single-RV shape.
94 *
95 * Carries the parsed distribution and the @c [lo, hi] support after
96 * intersection with the conditioning event (or the natural support
97 * when @c truncated == @c false). Either bound may be infinite when
98 * the RV's natural support or the event leaves the corresponding
99 * side unbounded (e.g. @c Normal | @c X > 0 yields
100 * @c (0, +&infin;)).
101 */
103 DistributionSpec spec; ///< Parsed kind + parameters
104 double lo; ///< Lower bound (-INF if unbounded)
105 double hi; ///< Upper bound (+INF if unbounded)
106 bool truncated; ///< True iff the bounds came from a
107 ///< non-trivial @c event_root
108};
109
110/**
111 * @brief Detect a closed-form, optionally-truncated single-RV shape.
112 *
113 * Common shape-detection helper shared by every closed-form
114 * single-RV consumer:
115 * - @c try_truncated_closed_form (truncated moments,
116 * @c Expectation.cpp);
117 * - @c try_truncated_closed_form_sample (rejection-free sampling,
118 * @c MonteCarloSampler.cpp);
119 * - @c rv_analytical_curves (PDF / CDF overlay,
120 * @c RvAnalyticalCurves.cpp).
121 *
122 * Returns @c std::nullopt when the shape is not tractable:
123 * - @p root is not a bare @c gate_rv;
124 * - the gate's @c extra does not parse as a @c DistributionSpec;
125 * - @p event_root resolves to @c gate_zero (event already decided
126 * infeasible by @c runRangeCheck);
127 * - @c collectRvConstraints fails (incomplete walk);
128 * - the resulting interval is empty or degenerate
129 * (@c lo @>= @c hi).
130 *
131 * When @p event_root is omitted or resolves to @c gate_one, the
132 * returned @c TruncatedSingleRv carries the RV's natural support and
133 * @c truncated = @c false; callers that don't distinguish the
134 * conditional and unconditional cases (e.g. the analytical-curves
135 * x-range chooser) can read uniformly off the result.
136 */
137std::optional<TruncatedSingleRv>
139 std::optional<gate_t> event_root);
140
141/**
142 * @brief True iff the conditioning event is provably infeasible for
143 * a bare @c gate_rv root.
144 *
145 * Distinguishes "event proved infeasible" (event resolves to
146 * @c gate_zero, or @c collectRvConstraints intersects to an empty
147 * interval) from "shape unsupported by @c matchTruncatedSingleRv"
148 * (return @c std::nullopt that just means "fall back to MC").
149 *
150 * Used by the conditional-moment dispatcher to raise an explicit
151 * infeasibility error before falling through to MC rejection – MC
152 * would still detect the same condition by accepting 0 of N samples,
153 * but the closed-form predicate spots it without ten thousand
154 * wasted draws and emits a tighter message.
155 *
156 * Returns @c false for non-@c gate_rv roots and for roots whose
157 * event/support pair is not provably infeasible by this cheap pass
158 * (the caller can still proceed to MC).
159 */
161 std::optional<gate_t> event_root);
162
163/**
164 * @brief Point mass at a finite scalar value (a @c gate_value root, or
165 * an @c as_random(c) leaf surfaced as a @c gate_value).
166 *
167 * Unconditional only; @c matchClosedFormDistribution bails when an
168 * event_root is supplied for a Dirac root. Carries no probability
169 * field: a Dirac root standalone has total mass 1, and inside a
170 * @c BernoulliMixtureShape the parent's @c p / 1-p weight is applied
171 * by the curve renderer.
172 */
174 double value;
175};
176
177/**
178 * @brief Categorical distribution over a finite outcome set.
179 *
180 * Matches a categorical-form @c gate_mixture
181 * (@c isCategoricalMixture): the key @c gate_input is ignored (its
182 * own probability is irrelevant – the mass is on the @c mulinputs),
183 * each remaining wire contributes one @c {value, prob} pair.
184 *
185 * Unconditional only; conditioning on a categorical's value is
186 * handled upstream by @c RangeCheck folding the cmp into a Bernoulli,
187 * leaving no event_root for this matcher to see.
188 */
190 std::vector<std::pair<double, double>> outcomes; ///< (value, mass) pairs
191};
192
193struct BernoulliMixtureShape; // forward (variant cycle)
194
195/**
196 * @brief One of the closed-form shapes the analytical-curves payload
197 * can render: bare RV (continuous PDF/CDF), Dirac (point mass),
198 * categorical (multiple point masses), or Bernoulli mixture
199 * of any two of the above.
200 */
205
206/**
207 * @brief Bernoulli mixture (@c gate_mixture with the
208 * @c [p_token, x_token, y_token] shape).
209 *
210 * @c p is @c getProb(p_token) when @c p_token is a bare
211 * @c gate_input; a compound Boolean @c p_token bails (its probability
212 * would require a recursive @c probability_evaluate call, out of
213 * scope for the static predicate). @c left and @c right recursively
214 * match the two arms; either may itself be a mixture, a bare RV, a
215 * Dirac, or a categorical, but always unconditional (truncation
216 * under a mixture is deferred until a real query needs it).
217 */
219 double p;
220 std::shared_ptr<ClosedFormShape> left;
221 std::shared_ptr<ClosedFormShape> right;
222};
223
224/**
225 * @brief Detect any of the closed-form shapes supported by
226 * @c rv_analytical_curves.
227 *
228 * Generalisation of @c matchTruncatedSingleRv that adds Bernoulli
229 * mixtures, categoricals, and Dirac (scalar @c gate_value) roots.
230 * Conditioning (@p event_root) is honoured for bare RV roots only;
231 * Dirac / categorical / mixture roots bail when the event isn't
232 * @c gate_one (the post-load-simplification "always true" default).
233 *
234 * Returns @c std::nullopt when none of the supported shapes match;
235 * callers fall back to histogram-only rendering.
236 */
237std::optional<ClosedFormShape>
239 std::optional<gate_t> event_root);
240
241/**
242 * @brief Exact histogram (bin_lo, bin_hi, probability mass) of a closed-form
243 * @p shape, in @p bins equal-width bins over its natural range.
244 *
245 * Defined in @c RvAnalyticalCurves.cpp (where the shape CDF / range helpers
246 * live). Lets @c rv_histogram answer for a closed-form distribution without
247 * sampling -- e.g. a truncated Gaussian under @c provsql.rv_mc_samples = 0.
248 * Returns @c std::nullopt when the range is degenerate or the CDF is
249 * unavailable, so the caller can fall back to Monte Carlo.
250 */
251std::optional<std::vector<std::tuple<double, double, double>>>
252analyticalHistogram(const ClosedFormShape &shape, int bins);
253
254/**
255 * @brief Run the support-based pruning pass over @p gc.
256 *
257 * For every @c gate_cmp in the circuit, computes the interval of
258 * @c (lhs - rhs) via interval arithmetic over @c gate_value,
259 * @c gate_rv, and @c gate_arith leaves; when the interval is
260 * provably above, below, or disjoint from zero, replaces the
261 * @c gate_cmp by a Bernoulli @c gate_input carrying the decided
262 * probability (0 or 1).
263 *
264 * Comparators whose interval is inconclusive (overlaps zero) are
265 * left intact for downstream passes.
266 *
267 * Iterates every gate (rather than walking from a specific root) so
268 * that a single sweep at @c getGenericCircuit time benefits every
269 * downstream consumer regardless of which sub-circuit they later
270 * traverse.
271 *
272 * @param gc Circuit to mutate in place.
273 * @return Number of comparators resolved by this pass.
274 */
275unsigned runRangeCheck(GenericCircuit &gc);
276
277/**
278 * @brief Probability-side pre-pass that rewrites @c gate_cmp gates
279 * provably true on the agg's value-interval to a @c gate_plus
280 * over the agg's per-row K-gates.
281 *
282 * Catches the always-true HAVING case (e.g. @c COUNT <= K with
283 * @c K >= N) that @c runRangeCheck deliberately leaves alone (the
284 * universally-sound @c gate_one rewrite would credit the empty
285 * world). Sound only in absorptive semirings; restricted to the
286 * probability-evaluate path.
287 *
288 * @return Number of comparators rewritten. See the implementation
289 * doc comment in @c RangeCheck.cpp for the full contract.
290 */
292
293} // namespace provsql
294
295#endif // PROVSQL_RANGE_CHECK_H
gate_t
Strongly-typed gate identifier.
Definition Circuit.h:49
Semiring-agnostic in-memory provenance circuit.
Continuous random-variable helpers (distribution parsing, moments).
In-memory provenance circuit with semiring-generic evaluation.
std::pair< double, double > compute_support(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event_root)
Compute the [lo, hi] support interval of a scalar sub-circuit rooted at root.
std::optional< ClosedFormShape > matchClosedFormDistribution(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event_root)
Detect any of the closed-form shapes supported by rv_analytical_curves.
std::variant< TruncatedSingleRv, DiracShape, CategoricalShape, BernoulliMixtureShape > ClosedFormShape
One of the closed-form shapes the analytical-curves payload can render: bare RV (continuous PDF/CDF),...
Definition RangeCheck.h:201
std::optional< std::vector< std::tuple< double, double, double > > > analyticalHistogram(const ClosedFormShape &shape, int bins)
Exact histogram (bin_lo, bin_hi, probability mass) of a closed-form shape, in bins equal-width bins o...
unsigned runRangeCheck(GenericCircuit &gc)
Run the support-based pruning pass over gc.
bool eventIsProvablyInfeasible(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event_root)
True iff the conditioning event is provably infeasible for a bare gate_rv root.
std::optional< std::pair< double, double > > collectRvConstraints(const GenericCircuit &gc, gate_t event_root, gate_t target_rv)
Walk event_root collecting rv op c constraints on target_rv.
std::optional< TruncatedSingleRv > matchTruncatedSingleRv(const GenericCircuit &gc, gate_t root, std::optional< gate_t > event_root)
Detect a closed-form, optionally-truncated single-RV shape.
unsigned runHavingAlwaysTrueRewriter(GenericCircuit &gc)
Probability-side pre-pass: rewrite HAVING-style gate_cmp gates that are provably TRUE on the agg's va...
Bernoulli mixture (gate_mixture with the [p_token, x_token, y_token] shape).
Definition RangeCheck.h:218
std::shared_ptr< ClosedFormShape > right
Definition RangeCheck.h:221
std::shared_ptr< ClosedFormShape > left
Definition RangeCheck.h:220
Categorical distribution over a finite outcome set.
Definition RangeCheck.h:189
std::vector< std::pair< double, double > > outcomes
(value, mass) pairs
Definition RangeCheck.h:190
Point mass at a finite scalar value (a gate_value root, or an as_random(c) leaf surfaced as a gate_va...
Definition RangeCheck.h:173
Parsed distribution spec (kind + up to two parameters).
Detection result for a closed-form, optionally-truncated single-RV shape.
Definition RangeCheck.h:102
double lo
Lower bound (-INF if unbounded).
Definition RangeCheck.h:104
DistributionSpec spec
Parsed kind + parameters.
Definition RangeCheck.h:103
double hi
Upper bound (+INF if unbounded).
Definition RangeCheck.h:105
bool truncated
True iff the bounds came from a non-trivial event_root.
Definition RangeCheck.h:106