/**
* @file HybridEvaluator.h
* @brief Peephole simplifier for continuous @c gate_arith sub-circuits.
*
* The hybrid evaluator owns the simplification + island-decomposition
* passes invoked between the universal cmp-resolution (@c RangeCheck)
* and the probability-specific analytic-CDF resolution
* (@c AnalyticEvaluator). This header exposes the simplifier; the
* island decomposer will be added alongside in the same namespace.
*
* The term @e peephole comes from compiler engineering (McKeeman, CACM
* 8(7), 1965): a small sliding window over consecutive instructions /
* gates, a fixed list of local pattern -> replacement rules, iterated
* to a fixed point. Each rule below looks at one @c gate_arith plus
* its immediate children, never further, which is exactly the
* peephole scope. Contrast with @c RangeCheck (global interval-
* propagation walk) and @c runHybridDecomposer (union-find over
* base-RV footprints across the whole circuit).
*
* The simplifier rewrites @c gate_arith gates in-place using closure
* rules that preserve the gate's scalar value in every world:
* - Constant folding of @c gate_arith subtrees over only @c gate_value
* leaves – collapses to a single @c gate_value.
* - Multiplicative zero short-circuit: @c TIMES with any constant-zero
* wire becomes @c gate_value:0, regardless of the other (possibly
* non-constant) wires.
* - Identity-element drops: remove @c gate_value:0 wires from a
* @c PLUS gate; remove @c gate_value:1 wires from a @c TIMES gate.
* - Mixture lift: a @c PLUS / @c TIMES over a single @c gate_mixture
* child pushes the other wires inside the branches. For the classic
* 3-wire shape mixture(p, X, Y) each branch becomes a fresh
* arith over the lifted wires; for the categorical N-wire shape
* mixture(key, mul_1, ..., mul_n) the constant offset / factor
* is folded directly into each mulinput's value text (sharing the key
* keeps the new mixture correlated with the original).
* - PLUS coefficient aggregation: a @c PLUS whose wires decompose as
* a_i·Z_i + b_i merges same-@c Z_i terms by summing
* coefficients (so @c X+X folds to @c 2·X, @c X-X to @c 0, etc.) and
* consolidates the constant offsets.
* - Scalar-times-RV closure: c·X for @c X a @c gate_rv whose
* distribution admits a closed-form scale (Normal, Uniform, Exp,
* Erlang) folds to a single scaled @c gate_rv.
* - Normal-family closure: a @c PLUS over linear combinations of
* independent normal @c gate_rv leaves folds to a single normal
* @c gate_rv whose mean and variance are the closed-form
* combinations. The independence test mirrors @c Expectation's
* footprint check: each contributing normal must be reached through
* a disjoint base-RV UUID.
* - Erlang-family closure: a @c PLUS over k ≥ 2 i.i.d. exponential
* @c gate_rv leaves with the same rate λ and pairwise-distinct
* UUIDs folds to a single Erlang(k, λ) @c gate_rv.
*
* The rules form a fixed-point loop per gate: after any rewrite
* succeeds, the gate is re-evaluated under all rules until none fire.
* Compositions like arith(NEG, arith(PLUS, value, value))
* therefore collapse fully in one bottom-up pass.
*
* The simplifier runs in two passes for shared-RV identity preservation:
* pass 1 applies every rule EXCEPT the scalar-times-RV closure so the
* aggregator gets to see @c c·X-shaped wires inside a parent PLUS with
* their underlying RV identity intact (otherwise a bottom-up fold of
* the inner TIMES would mint a fresh @c gate_rv there and decouple it
* from a sibling reference to the same @c X). Pass 2 then folds the
* remaining @c TIMES wrappers with the scalar-times-RV rule.
*
* Soundness: every rule produces a circuit semantically equivalent to
* the original in every world (same scalar distribution, same support,
* same comparator outcomes). The closures merge independent leaves
* into a new leaf with a fresh distribution; downstream consumers
* (sampler, RangeCheck, AnalyticEvaluator, Expectation) see the new
* leaf and use the closed-form distribution machinery as if the user
* had written it directly.
*
* Operates on the in-memory @c GenericCircuit only; the persistent
* mmap store is never mutated. Children that become orphaned by a
* rewrite are not reaped here.
*
* Gated by the @c provsql.hybrid_evaluation GUC (default on):
* @c probability_evaluate skips the pass entirely when the GUC is off,
* letting users verify that the analytic path and the whole-circuit
* MC fallback agree on the same query.
*/
#ifndef PROVSQL_HYBRID_EVALUATOR_H
#define PROVSQL_HYBRID_EVALUATOR_H
#include "GenericCircuit.h"
namespace provsql {
/**
* @brief Run the peephole simplifier over @p gc.
*
* Visits every gate in post-order and applies the closure rules
* described in the header comment until a fixed point is reached.
*
* @param gc Circuit to mutate in place.
* @return Number of gate rewrites performed by this pass.
*/
unsigned runHybridSimplifier(GenericCircuit &gc);
/**
* @brief Constant-fold pass over every @c gate_arith in @p gc.
*
* Walks the circuit bottom-up and replaces any @c gate_arith whose
* children all evaluate to scalar constants with the equivalent
* @c gate_value (e.g. @c arith(NEG, value:2) becomes @c value:-2,
* @c arith(PLUS, value:1, value:2) becomes @c value:3).
*
* Strictly a subset of @c runHybridSimplifier (only @c try_eval_constant
* fires; no family closures, identity drops, or mixture lifts), and
* therefore safe to run at load time alongside @c runRangeCheck and
* @c foldSemiringIdentities: the resulting @c gate_value gates carry
* no random identity, so no consumer's shared-RV coupling is broken
* by the rewrite. The family closures stay behind the separate
* @c hybrid_evaluation GUC because they replace a multi-leaf subtree
* with a fresh @c gate_rv UUID and would decouple shared base RVs
* that other parts of the circuit reference.
*
* Lifts the @c -c::random_variable parser quirk (which builds an
* @c arith(NEG, value:c) gate rather than @c value:-c) into a clean
* @c gate_value before downstream consumers like
* @c collectRvConstraints / @c asRvVsConstCmp inspect the circuit.
*
* @return Number of gates rewritten.
*/
unsigned runConstantFold(GenericCircuit &gc);
/**
* @brief Collapse degenerate Bernoulli @c gate_mixture gates whose
* selector is certainly true (pi = 1) or certainly false
* (pi = 0) to the corresponding arm.
*
* A classic 3-wire mixture @c mixture(p, X, Y) with @c P(p) = 1 is
* distributionally @c X (and @c Y when @c P(p) = 0). The collapse is
* exact and correlation-safe precisely at those two points: a
* deterministic selector carries no coupling, so folding it does not
* decouple a selector shared across mixtures -- which is why only the
* exact 0/1 cases are admitted (a fractional pi genuinely couples the
* arms and must reach the sampler intact). pi is taken only where it
* is known without a probability computation: a resolved
* @c gate_one / @c gate_zero selector, or a bare @c gate_input whose
* pinned probability is 1 or 0 (the latter includes the default 1 of a
* provenance-tracked but non-probabilistic tuple). A compound Boolean
* selector is left for the probability-aware evaluators.
*
* The mixture is rewritten as a single-wire @c gate_arith @c PLUS
* passthrough to the surviving arm (@c liftConditionedToTarget), so it
* references rather than copies the survivor -- a shared survivor keeps
* its single gate identity. Runs at load time before
* @c runConstantFold and @c foldSemiringIdentities, which then reduce
* the resulting passthroughs and any now-constant enclosing arithmetic
* (e.g. the provenance-weighted count in @c avg's denominator collapses
* to a @c gate_value, letting the analytic evaluator divide by it).
*
* @return Number of mixtures collapsed.
*/
unsigned foldDegenerateMixtures(GenericCircuit &gc);
/**
* @brief Marginalise unresolved continuous-island @c gate_cmp gates
* into Bernoulli @c gate_input leaves.
*
* Runs after @c RangeCheck, the simplifier (@c runHybridSimplifier),
* and @c AnalyticEvaluator have done what they can. Picks up the
* residual comparators whose two sides are an entirely continuous
* island (subtree of @c gate_value, @c gate_rv, @c gate_arith with
* no Boolean structure underneath) but whose specific shape is not
* one the analytic CDF resolver handles – e.g.
* Normal + Uniform > 0, heterogeneous-rate sums of
* exponentials, or other compositions the simplifier could not fold
* to a bare distribution leaf.
*
* Each qualifying comparator is marginalised by drawing @p samples
* worlds and applying the comparator scalar-by-scalar; the empirical
* probability replaces the @c gate_cmp via
* @c resolveCmpToBernoulli. The circuit downstream becomes purely
* Boolean, so the existing @c independent / @c tree-decomposition /
* compilation methods become available on circuits that would
* otherwise have to fall through to whole-circuit MC.
*
* **Singleton groups** are marginalised into a single
* @c gate_input via @c GenericCircuit::resolveCmpToBernoulli.
*
* **Multi-cmp shared-island groups** (k comparators sharing one or
* more base @c gate_rv leaves, detected via pairwise footprint
* overlap with union-find) are resolved by inlining a 2^k joint
* distribution table:
* - One anonymous @c gate_input acts as the block key.
* - One @c gate_mulinput per joint outcome with positive probability,
* all sharing the key, carries the joint mass (mutually-exclusive
* block).
* - Each comparator is rewritten as @c gate_plus over the mulinputs
* whose joint outcome word has the comparator's bit set.
* The downstream OR over the rewritten comparators thereby observes
* the dependent joint distribution: mulinputs across comparators
* dedup at OR sites in
* @c BooleanCircuit::independentEvaluationInternal (or are
* Bayesian-tree-rewritten by @c rewriteMultivaluedGates before
* @c tree-decomposition / @c monte-carlo / external compilers).
* Groups with k > @c JOINT_TABLE_K_MAX (currently 8, i.e. 256
* outcomes) fall through to whole-circuit MC to keep the
* materialisation bounded.
*
* @param gc Circuit to mutate in place.
* @param samples Number of MC iterations used per marginalisation.
* Callers typically pass @c provsql_rv_mc_samples.
* @return Number of comparators resolved by this pass.
*/
unsigned runHybridDecomposer(GenericCircuit &gc, unsigned samples);
} // namespace provsql
#endif // PROVSQL_HYBRID_EVALUATOR_H