\set ECHO none
\pset format unaligned

-- ==========================================================================
-- Order statistics over random_variable: the same-row provsql.greatest /
-- provsql.least constructors and the max / min aggregates, both lowering to a
-- gate_arith MAX / MIN opcode.  MC-backed here (seed pinned); the closed forms
-- for i.i.d. families are asserted in continuous_analytic.  Support propagation
-- (RangeCheck) is exact and independent of MC.
-- ==========================================================================

SET provsql.active = on;
SET provsql.monte_carlo_seed = 42;
SET provsql.rv_mc_samples = 200000;
SET search_path TO public, provsql;

CREATE TABLE d AS SELECT uniform(0,1) AS x, uniform(0,1) AS y, uniform(0,1) AS z;

-- Same-row greatest / least of three i.i.d. U(0,1).  E[max] = n/(n+1) = 3/4,
-- E[min] = 1/(n+1) = 1/4, Var[max] = Var(Beta(3,1)) = 3/80 = 0.0375.
SELECT abs(expected(provsql.greatest(x,y,z)) - 0.75)    < 0.02  AS e_max_ok,
       abs(expected(provsql.least(x,y,z))    - 0.25)    < 0.02  AS e_min_ok,
       abs(variance(provsql.greatest(x,y,z)) - 3.0/80)  < 0.02  AS v_max_ok
  FROM d;

-- Support of a same-row order statistic is exact (interval propagation, no MC):
-- max / min of U(0,1) columns stays in [0, 1].
SELECT s.lo = 0 AS max_lo_0, s.hi = 1 AS max_hi_1
  FROM d, support(provsql.greatest(x,y,z)) s;

-- Two-argument greatest: E[max of two U(0,1)] = 2/3.
SELECT abs(expected(provsql.greatest(uniform(0,1), uniform(0,1))) - 2.0/3) < 0.02
       AS e_max2_ok;

-- Single argument (and NULL handling): greatest(x) is x; an all-NULL / empty
-- call is NULL.  Compare the underlying uuids -- an RV `=` would itself be
-- lifted into a comparison-event token, not a boolean.
WITH r AS (SELECT uniform(0,1) AS u)
SELECT provsql.greatest(u)::uuid = u::uuid AS greatest_singleton,
       provsql.greatest(NULL::random_variable) IS NULL AS greatest_all_null
  FROM r;

-- max / min aggregates over a column of i.i.d. U(0,1): same references.
WITH s(r) AS (VALUES (uniform(0,1)),(uniform(0,1)),(uniform(0,1)))
SELECT abs(expected(max(r)) - 0.75) < 0.02 AS agg_max_ok,
       abs(expected(min(r)) - 0.25) < 0.02 AS agg_min_ok
  FROM s;

-- Empty-group identity: max over zero rows is -inf, min over zero rows is +inf
-- (the extremum identities, the counterpart to sum's 0 / product's 1).
WITH s(r) AS (SELECT uniform(0,1) WHERE false)
SELECT expected(max(r)) = '-Infinity'::float8 AS empty_max_neg_inf,
       expected(min(r)) = 'Infinity'::float8  AS empty_min_pos_inf
  FROM s;

-- End-to-end through the provenance-tracked aggregate path: each row's argument
-- is wrapped in mixture(prov, X, as_random(0)) and the FFUNC patches the else
-- branch to the -inf identity, so a prob-1 group recovers E[max] = 3/4.
CREATE TABLE os_sensors(id text, reading random_variable);
INSERT INTO os_sensors VALUES
  ('a', uniform(0,1)), ('b', uniform(0,1)), ('c', uniform(0,1));
SELECT add_provenance('os_sensors');
CREATE TABLE os_peak AS SELECT max(reading) AS m FROM os_sensors;
SELECT remove_provenance('os_peak');
SELECT abs(expected(m) - 0.75) < 0.02 AS tracked_max_ok FROM os_peak;
DROP TABLE os_peak;
DROP TABLE os_sensors;

-- Closed-form order statistics (MC disabled): i.i.d. Uniform / Exponential
-- resolve exactly, no sampling.
SET provsql.rv_mc_samples = 0;

-- i.i.d. Uniform(0,1): E[max_n] = n/(n+1), E[min_n] = 1/(n+1), exact.
SELECT expected(provsql.greatest(x,y,z)) = 0.75 AS iid_unif_max_exact,
       expected(provsql.least(x,y,z))    = 0.25 AS iid_unif_min_exact
  FROM d;

-- i.i.d. Exponential(λ): E[min] = 1/(nλ), E[max] = H_n/λ.
SELECT expected(provsql.least(exponential(1), exponential(1))) = 0.5 AS iid_exp_min_exact,
       expected(provsql.greatest(exponential(1), exponential(1))) = 1.5 AS iid_exp_max_exact;

-- General Uniform(a,b): E[max of 2 U(2,6)] = 2 + 4*2/3 = 14/3.
SELECT abs(expected(provsql.greatest(uniform(2,6), uniform(2,6))) - 14.0/3) < 1e-12
       AS unif_ab_max_exact;

-- Mixed independent families via the layer-cake quadrature
-- E[max] = ∫(1-∏F_i), E[min] = ∫∏(1-F_i).  For U(0,1) and Exp(1):
-- E[min] = ∫_0^1 (1-t) e^{-t} dt = 1/e; E[max] = 1.5 - 1/e (exact references).
SELECT abs(expected(provsql.least(uniform(0,1), exponential(1))) - exp(-1)) < 1e-6
         AS mixed_min_quadrature,
       abs(expected(provsql.greatest(uniform(0,1), exponential(1)))
             - (1.5 - exp(-1))) < 1e-6
         AS mixed_max_quadrature;

-- The builtin GREATEST / LEAST grammar is lifted over random_variable args
-- into the same order statistic, so bare greatest(x,y,z) works and matches
-- the schema-qualified form exactly.
SELECT expected(greatest(x,y,z)) = 0.75 AS bare_greatest_exact,
       expected(least(x,y,z))    = 0.25 AS bare_least_exact
  FROM d;

-- Idempotence: greatest(x,x,y) de-duplicates to greatest(x,y) -- the same
-- gate -- and greatest(x) collapses to x.
SELECT (greatest(x,x,y))::uuid = (greatest(x,y))::uuid AS dedup_same_gate,
       (greatest(x))::uuid     = (x)::uuid             AS singleton_collapses,
       expected(greatest(x,x,y)) = expected(greatest(x,y)) AS dedup_same_value
  FROM d;

-- A base RV shared between both arms of a min/max must stay coupled through
-- the comparison-probability path, exactly as it does through expected /
-- quantile.  cost = min(T0+T1, T0+T2) with T0 ~ N(10,2) SHARED, T1 ~ N(5,1),
-- T2 ~ N(6,1.5): the correlated Pr(cost <= 15) ~ 0.558 (matching quantile
-- inversion / MC); the independent-arms value 1-(1-F1)(1-F2) ~ 0.672 is the
-- decoupling-bug signature.  The comparison probability must agree with the
-- quantile on the SAME rv (one rv, one CDF).
SET provsql.rv_mc_samples = 200000;
WITH shared AS (
  SELECT provsql.least(t0 + normal(5,1), t0 + normal(6,1.5)) AS cost
    FROM (SELECT normal(10,2) AS t0) s
)
SELECT abs(probability_evaluate(rv_cmp_le(cost, as_random(15))) - 0.558) < 0.02
         AS min_shared_leaf_correlated,
       abs(quantile(cost, 0.558) - 15.0) < 0.2
         AS quantile_consistent
  FROM shared;

RESET provsql.rv_mc_samples;

-- Ordering / comparing a random_variable directly is meaningless (a
-- distribution, not a scalar): ORDER BY over >1 row raises a clear error.
DO $$
BEGIN
  PERFORM r FROM (VALUES (uniform(0,1)),(uniform(0,1))) v(r) ORDER BY r;
  RAISE NOTICE 'rv_order_by_rejected=%', false;
EXCEPTION WHEN OTHERS THEN
  RAISE NOTICE 'rv_order_by_rejected=%', true;
END
$$;

DROP TABLE d;

SELECT 'ok'::text AS continuous_order_statistics_done;
