\set ECHO none
\pset format unaligned
SET search_path TO provsql_test,provsql;

-- The sampler uses std::mt19937_64 seeded from provsql.monte_carlo_seed
-- so all assertions in this file are deterministic across runs.
SET provsql.monte_carlo_seed = 42;

-- Helper: build a cmp gate over two random_variable arguments and one
-- comparison operator OID, then evaluate it via Monte Carlo.  The
-- comparison operator is the standard float8 comparator: every gate_rv
-- and gate_value child is scalar-valued, so the sampler produces
-- doubles for both sides and applies the comparator.
CREATE OR REPLACE FUNCTION mc_prob(
  l random_variable, r random_variable, op text, samples int
) RETURNS double precision AS
$$
  SELECT provsql.probability_evaluate(
    provsql.provenance_cmp(
      (l)::uuid,
      (op || '(double precision,double precision)')::regoperator::oid,
      (r)::uuid),
    'monte-carlo', samples::text)
$$ LANGUAGE sql VOLATILE;

-- Per-iteration scalar memoisation: when the same gate_rv UUID feeds
-- both sides of a comparator, the two evaluations must use the SAME
-- draw, so the comparator collapses to "x = x" / "x > x" / etc.
WITH r AS (SELECT provsql.normal(0, 1) AS rv)
SELECT
  provsql.probability_evaluate(
    provsql.provenance_cmp(
      (rv)::uuid,
      '=(double precision,double precision)'::regoperator::oid,
      (rv)::uuid),
    'monte-carlo', '1000') = 1.0 AS rv_equals_itself,
  provsql.probability_evaluate(
    provsql.provenance_cmp(
      (rv)::uuid,
      '<(double precision,double precision)'::regoperator::oid,
      (rv)::uuid),
    'monte-carlo', '1000') = 0.0 AS rv_lt_itself
FROM r;

-- Distribution sampling: P(N(0, 1) > 0) is exactly 0.5 by symmetry;
-- a 100k-sample MC pull should be within 0.01 of the truth.
SELECT abs(mc_prob(provsql.normal(0, 1), provsql.as_random(0), '>', 100000) - 0.5)
       < 0.01 AS normal_gt_zero_within_tolerance;

-- P(U(0, 1) <= 0.3) = 0.3
SELECT abs(mc_prob(provsql.uniform(0, 1), provsql.as_random(0.3), '<=', 100000) - 0.3)
       < 0.01 AS uniform_le_0_3_within_tolerance;

-- P(Exp(1) > 1) = 1/e ≈ 0.367879
SELECT abs(mc_prob(provsql.exponential(1), provsql.as_random(1), '>', 100000) - 0.36787944117)
       < 0.01 AS exponential_gt_1_within_tolerance;

-- Erlang sampling: wrap in a no-op arith gate so AnalyticEvaluator's
-- closed-form CDF (which only fires on bare gate_rv leaves) doesn't
-- resolve the cmp before the sampler sees it.  For Erlang(3, 1),
-- P(X > 3) = e^{-3}(1 + 3 + 4.5) = 8.5 / e^3 ≈ 0.4232.
SELECT abs(mc_prob(provsql.erlang(3, 1) + provsql.as_random(0),
                   provsql.as_random(3), '>', 100000) - 8.5 * exp(-3.0))
       < 0.01 AS erlang_gt_3_within_tolerance;

-- Determinism: same seed -> same MC result, even with a non-trivial
-- sample count.  Run twice, expect bit-identical doubles.
SELECT mc_prob(provsql.normal(0, 1), provsql.as_random(0), '>', 5000)
     = mc_prob(provsql.normal(0, 1), provsql.as_random(0), '>', 5000) AS deterministic;

-- Hand-built gate_arith over two independent normals: P(X + Y > 0) is
-- exactly 0.5 because X+Y ~ N(0, 2).  The arith gate's info1 holds
-- the PROVSQL_ARITH_PLUS tag (= 0).  We hand-build the circuit here
-- to exercise the gate_arith branch of the sampler directly, without
-- relying on the user-facing rv arithmetic operators.
DO $$
DECLARE
  x uuid := (provsql.normal(0, 1))::uuid;
  y uuid := (provsql.normal(0, 1))::uuid;
  zero uuid := (provsql.as_random(0))::uuid;
  sum_token uuid := public.uuid_generate_v4();
  cmp_token uuid;
  p double precision;
BEGIN
  PERFORM provsql.create_gate(sum_token, 'arith', ARRAY[x, y]);
  PERFORM provsql.set_infos(sum_token, 0);  -- PROVSQL_ARITH_PLUS = 0
  cmp_token := provsql.provenance_cmp(
    sum_token,
    '>(double precision,double precision)'::regoperator::oid,
    zero);
  p := provsql.probability_evaluate(cmp_token, 'monte-carlo', '100000');
  IF abs(p - 0.5) >= 0.01 THEN
    RAISE EXCEPTION 'arith PLUS sampler P(X+Y>0)=% out of tolerance', p;
  END IF;
END $$;

-- Same circuit, structurally-dependent variant: arith PLUS over the
-- *same* x twice gives 2x, and P(2x > 0) = P(x > 0) = 0.5 – but the
-- per-iteration memoisation must produce the same x twice.
DO $$
DECLARE
  x uuid := (provsql.normal(0, 1))::uuid;
  zero uuid := (provsql.as_random(0))::uuid;
  sum_token uuid := public.uuid_generate_v4();
  cmp_token uuid;
  p double precision;
BEGIN
  PERFORM provsql.create_gate(sum_token, 'arith', ARRAY[x, x]);
  PERFORM provsql.set_infos(sum_token, 0);
  cmp_token := provsql.provenance_cmp(
    sum_token,
    '>(double precision,double precision)'::regoperator::oid,
    zero);
  p := provsql.probability_evaluate(cmp_token, 'monte-carlo', '100000');
  IF abs(p - 0.5) >= 0.01 THEN
    RAISE EXCEPTION 'arith PLUS (same-x twice) sampler P out of tolerance: %', p;
  END IF;
END $$;

-- gate_arith TIMES, MINUS, DIV, NEG round-trip through the sampler.
-- We don't pin tight tolerances here – just sanity-check that the
-- branches don't throw and the result is a finite probability.
DO $$
DECLARE
  x uuid := (provsql.uniform(1, 2))::uuid;
  y uuid := (provsql.uniform(1, 2))::uuid;
  zero uuid := (provsql.as_random(0))::uuid;
  arith_token uuid;
  cmp_token uuid;
  p double precision;
  op_tag int;
BEGIN
  -- TIMES (1)
  arith_token := public.uuid_generate_v4();
  PERFORM provsql.create_gate(arith_token, 'arith', ARRAY[x, y]);
  PERFORM provsql.set_infos(arith_token, 1);
  cmp_token := provsql.provenance_cmp(arith_token,
    '>(double precision,double precision)'::regoperator::oid,
    zero);
  p := provsql.probability_evaluate(cmp_token, 'monte-carlo', '1000');
  IF p <> 1.0 THEN
    RAISE EXCEPTION 'arith TIMES of two U(1,2) > 0 should be 1.0, got %', p;
  END IF;

  -- MINUS (2): U(1,2) - U(1,2) > 0 has probability 0.5
  arith_token := public.uuid_generate_v4();
  PERFORM provsql.create_gate(arith_token, 'arith', ARRAY[x, y]);
  PERFORM provsql.set_infos(arith_token, 2);
  cmp_token := provsql.provenance_cmp(arith_token,
    '>(double precision,double precision)'::regoperator::oid,
    zero);
  p := provsql.probability_evaluate(cmp_token, 'monte-carlo', '100000');
  IF abs(p - 0.5) >= 0.01 THEN
    RAISE EXCEPTION 'arith MINUS sampler P(X-Y>0)=% out of tolerance', p;
  END IF;

  -- NEG (4): -U(1,2) > 0 has probability 0
  arith_token := public.uuid_generate_v4();
  PERFORM provsql.create_gate(arith_token, 'arith', ARRAY[x]);
  PERFORM provsql.set_infos(arith_token, 4);
  cmp_token := provsql.provenance_cmp(arith_token,
    '>(double precision,double precision)'::regoperator::oid,
    zero);
  p := provsql.probability_evaluate(cmp_token, 'monte-carlo', '1000');
  IF p <> 0.0 THEN
    RAISE EXCEPTION 'arith NEG of U(1,2) > 0 should be 0.0, got %', p;
  END IF;
END $$;

-- All asserts above either return a Boolean t/f or raise; this single
-- final SELECT terminates the test with a stable "ok" line.
SELECT 'ok'::text AS continuous_sampler_done;

DROP FUNCTION mc_prob(random_variable, random_variable, text, int);
RESET provsql.monte_carlo_seed;
