\set ECHO none
\pset format unaligned

-- Exact conjugate-prior posteriors: when an and_agg evidence conjunction of
-- observe atoms matches a registered conjugate prior/likelihood pair (the
-- latent a bare all-literal leaf wired into ONE parameter slot of every
-- observed leaf), the posterior is computed in closed form -- exact,
-- deterministic, and available with Monte Carlo DISABLED.  Everything below
-- the "decline coverage" marker restores a sample budget and checks that
-- non-conjugate shapes still answer through importance sampling.

SET search_path TO provsql, public;
SET provsql.rv_mc_samples = 0;   -- exact only: any fall-through to sampling raises

-- ---------------------------------------------------------------------
-- Normal-Normal (the case-study-8 Problem 13 workload).  Prior
-- mu ~ N(0, 10); data x_i ~ N(mu, 1); data {8, 10, 12}:
--   posterior precision = 1/100 + 3 = 3.01,
--   mean = 30/3.01 = 9.9667774086...,  variance = 1/3.01 = 0.3322259136...
-- ---------------------------------------------------------------------

DO $$
DECLARE
  mu random_variable := normal(0, 10);
  ev uuid;
BEGIN
  SELECT and_agg(observe(normal(mu, 1), x)) INTO ev
    FROM (VALUES (8.0::float8), (10.0), (12.0)) AS t(x);
  RAISE NOTICE 'normal_normal_mean_exact: %',
    (abs(expected(mu, ev) - 30.0/3.01) < 1e-12);
  RAISE NOTICE 'normal_normal_var_exact: %',
    (abs(variance(mu, ev) - 1.0/3.01) < 1e-12);
  -- Gaussian posterior: the exact median is the mean; the exact 10%
  -- quantile is mean + z_{0.1}·sd (inverse CDF, ~1e-7 grade numerics).
  RAISE NOTICE 'normal_normal_median_exact: %',
    (abs(quantile(mu, 0.5, ev) - 30.0/3.01) < 1e-9);
  RAISE NOTICE 'normal_normal_q10_exact: %',
    (abs(quantile(mu, 0.1, ev) - (30.0/3.01 - 1.2815515655446004 * sqrt(1.0/3.01))) < 1e-4);
  -- Marginal likelihood P(data): the exact N(0, I + 100·11^T) density at
  -- (8,10,12), i.e. (2*pi)^{-3/2} · 301^{-1/2} · exp(-(308 - 90000/301)/2).
  RAISE NOTICE 'normal_normal_evidence_exact: %',
    (abs(evidence(ev)
         - exp(-(308.0 - 90000.0/301.0)/2.0) / (power(2*pi(), 1.5) * sqrt(301.0))) < 1e-18);
  -- Differential entropy of the Gaussian posterior: ln(2*pi*e*var)/2 nats
  -- (exact quadrature over the family pdf).
  RAISE NOTICE 'normal_normal_entropy_exact: %',
    (abs(entropy(mu, ev) - 0.5 * ln(2*pi()*exp(1.0)/3.01)) < 1e-3);
END $$;

-- ---------------------------------------------------------------------
-- Exact posterior sampling and histogram, still at rv_mc_samples = 0:
-- rv_sample draws i.i.d. from the posterior distribution (seeded), and
-- rv_histogram bins the exact analytical shape (masses sum to ~1).
-- ---------------------------------------------------------------------

SET provsql.monte_carlo_seed = 42;
DO $$
DECLARE
  mu random_variable := normal(0, 10);
  ev uuid;
  n bigint;
  m double precision;
  tot double precision;
BEGIN
  SELECT and_agg(observe(normal(mu, 1), x)) INTO ev
    FROM (VALUES (8.0::float8), (10.0), (12.0)) AS t(x);
  SELECT count(*), avg(s) INTO n, m FROM rv_sample((mu)::uuid, 20000, ev) s;
  RAISE NOTICE 'posterior_sample_count: %', n;
  RAISE NOTICE 'posterior_sample_mean_ok: %', (abs(m - 30.0/3.01) < 0.02);
  SELECT sum((b->>'count')::float8) INTO tot
    FROM jsonb_array_elements(rv_histogram((mu)::uuid, 25, ev)) b;
  RAISE NOTICE 'posterior_histogram_mass_ok: %', (abs(tot - 1.0) < 1e-3);
END $$;

-- ---------------------------------------------------------------------
-- Gamma-prior rate, three likelihood routes into the same carrier:
-- Exponential gaps, Poisson counts, and their MIXED interleaving (the
-- update is per observation against the running posterior, so mixed
-- likelihoods sharing one conjugate prior compose).  gamma(2, 1) with an
-- integer shape is stored as an erlang leaf; the fold canonicalises it
-- into the gamma carrier.
-- ---------------------------------------------------------------------

DO $$
DECLARE
  lam random_variable := gamma(2, 1);
  ev uuid;
BEGIN
  -- Exponential data {0.5, 1.0, 1.5}: Gamma(2+3, 1+3) = Gamma(5, 4).
  SELECT and_agg(observe(exponential(lam), x)) INTO ev
    FROM (VALUES (0.5::float8), (1.0), (1.5)) AS t(x);
  RAISE NOTICE 'gamma_exp_mean: %', expected(lam, ev);
  RAISE NOTICE 'gamma_exp_var: %', variance(lam, ev);
  -- Sequential Lomax predictives:
  --   2·1²/1.5³ · 3·1.5³/2.5⁴ · 4·2.5⁴/4⁵ = 24/1024 = 0.0234375.
  RAISE NOTICE 'gamma_exp_evidence_exact: %',
    (abs(evidence(ev) - 24.0/1024.0) < 1e-15);

  -- Poisson counts {3, 5}: Gamma(2+8, 1+2) = Gamma(10, 3), mean 10/3.
  SELECT and_agg(observe(poisson(lam), x)) INTO ev
    FROM (VALUES (3.0::float8), (5.0)) AS t(x);
  RAISE NOTICE 'gamma_poisson_mean_exact: %',
    (abs(expected(lam, ev) - 10.0/3.0) < 1e-12);
  -- Negative-binomial predictive factors, exact:
  --   m(3) = C(4,3)·(1/2)^5 ... folded: Gamma(2,1) -> Gamma(5,2) -> Gamma(10,3).
  RAISE NOTICE 'gamma_poisson_evidence_exact: %',
    (abs(evidence(ev)
         - (4.0*3*2/6) * power(0.5, 5)
           * (9.0*8*7*6/24) * power(2.0/3, 5) * power(1.0/3, 5)) < 1e-15);

  -- Mixed fold: one Poisson count (4) and one Exponential gap (2):
  -- Gamma(2+4+1, 1+1+2) = Gamma(7, 4), mean 7/4.
  SELECT provenance_times(observe(poisson(lam), 4),
                          observe(exponential(lam), 2.0)) INTO ev;
  RAISE NOTICE 'gamma_mixed_mean: %', expected(lam, ev);
END $$;

-- ---------------------------------------------------------------------
-- Beta-prior success probability: Binomial, Geometric (trials
-- convention), NegativeBinomial (failures convention).
-- ---------------------------------------------------------------------

DO $$
DECLARE
  p random_variable := beta(2, 3);
  ev uuid;
BEGIN
  -- Binomial(10, p) = 7: Beta(2+7, 3+3) = Beta(9, 6), mean 9/15 = 0.6.
  ev := observe(binomial(10, p), 7);
  RAISE NOTICE 'beta_binomial_mean: %', expected(p, ev);
  -- Beta-binomial predictive C(10,7)·B(9,6)/B(2,3), exact via factorials:
  -- B(9,6) = 8!·5!/14!, B(2,3) = 1!·2!/4!.
  RAISE NOTICE 'beta_binomial_evidence_exact: %',
    (abs(evidence(ev)
         - 120.0 * (40320.0 * 120.0 / 87178291200.0)
                 / (1.0 * 2.0 / 24.0)) < 1e-15);

  -- Geometric (trials on {1, 2, ...}), d = 4: Beta(2+1, 3+3) = Beta(3, 6).
  ev := observe(geometric(p), 4);
  RAISE NOTICE 'beta_geometric_mean_exact: %',
    (abs(expected(p, ev) - 1.0/3.0) < 1e-12);

  -- NegativeBinomial(r = 3, p) (failures on {0, 1, ...}), d = 5:
  -- Beta(2+3, 3+5) = Beta(5, 8), mean 5/13.
  ev := observe(negative_binomial(3, p), 5);
  RAISE NOTICE 'beta_negbinomial_mean_exact: %',
    (abs(expected(p, ev) - 5.0/13.0) < 1e-12);
END $$;

-- ---------------------------------------------------------------------
-- LogNormal-Normal (log-scale location), Pareto-Uniform (upper bound of
-- U(0, theta)), Gamma-Pareto (tail shape).
-- ---------------------------------------------------------------------

DO $$
DECLARE
  mu random_variable := normal(0, 10);
  theta random_variable := pareto(1, 2);
  alpha random_variable := gamma(3, 2);
  ev uuid;
BEGIN
  -- LogNormal(mu, 1) observed at e^2: a Normal update at ln d = 2, so the
  -- posterior mean is 2·100/101.
  ev := observe(lognormal(mu, 1), exp(2.0));
  RAISE NOTICE 'lognormal_normal_mean_exact: %',
    (abs(expected(mu, ev) - 200.0/101.0) < 1e-12);

  -- U(0, theta) observed at {0.5, 3}: Pareto(1, 2) -> Pareto(3, 4), mean
  -- 3·4/3 = 4.  (A nonzero literal lower bound is NOT conjugate and
  -- declines -- see the decline section.)
  SELECT and_agg(observe(uniform(0, theta), x)) INTO ev
    FROM (VALUES (0.5::float8), (3.0)) AS t(x);
  RAISE NOTICE 'pareto_uniform_mean: %', expected(theta, ev);
  -- Predictives: d=0.5 <= xm: alpha/((alpha+1)·xm) = 2/3;
  -- then Pareto(1,3) at d=3 > xm: 3·1³/(4·3⁴) = 3/324.
  RAISE NOTICE 'pareto_uniform_evidence_exact: %',
    (abs(evidence(ev) - (2.0/3.0) * (3.0/324.0)) < 1e-15);

  -- Pareto(1, alpha) observed at e: Gamma(3, 2) -> Gamma(4, 3), mean 4/3.
  ev := observe(pareto(1, alpha), exp(1.0));
  RAISE NOTICE 'gamma_pareto_mean_exact: %',
    (abs(expected(alpha, ev) - 4.0/3.0) < 1e-12);
END $$;

-- ---------------------------------------------------------------------
-- The conditional-equality surface routes here too: "R | (Y = d)" and
-- given(Y = d) rewrite the point event into an observe atom, so a bare
-- conjugate latent stays exact at rv_mc_samples = 0.
-- ---------------------------------------------------------------------

DO $$
DECLARE
  lam random_variable := gamma(2, 1);
BEGIN
  -- lam | (poisson(lam) = 3): Gamma(5, 2), mean 2.5.
  RAISE NOTICE 'equality_form_mean: %', expected(lam | (poisson(lam) = 3));
END $$;

-- ---------------------------------------------------------------------
-- Decline coverage: shapes one step outside the recogniser fall back to
-- importance sampling (restored sample budget) and stay CORRECT -- the
-- closed form computes the same estimand the sampler estimates.
-- ---------------------------------------------------------------------

SET provsql.rv_mc_samples = 300000;
SET provsql.monte_carlo_seed = 20260709;
SET provsql.ess_warn_fraction = 0;

DO $$
DECLARE
  mu random_variable := normal(0, 10);
  sig random_variable := gamma(2, 2);
  a random_variable := normal(0, 10);
  b random_variable := normal(0, 10);
  theta2 random_variable := pareto(2, 3);
  ev uuid;
BEGIN
  -- (a) latent reaching the leaf through gate_arith: IS, within tolerance
  -- of the affine-transformed closed form (posterior of mu given
  -- 2·mu + noise = 8 has mean 8·2·100/(4·100+1) = 1600/401 = 3.9900...).
  ev := observe(normal(mu + mu, 1), 8);
  RAISE NOTICE 'decline_arith_is_ok: %',
    (abs(expected(mu, ev) - 1600.0/401.0) < 0.05);

  -- (b) wired sigma slot: no registered rule (conjugacy is on the
  -- precision, not sigma); IS still answers.
  ev := observe(normal(0, sig), 1.5);
  RAISE NOTICE 'decline_sigma_slot_is_ok: %',
    (expected(sig, ev) > 0.5 AND expected(sig, ev) < 2.0);

  -- (c) a Boolean factor conjoined with the observations declines the
  -- recognition but not the semantics: an independent Bernoulli factor
  -- leaves the posterior unchanged.
  SELECT provenance_times(and_agg(observe(normal(mu, 1), x)),
                          rv_cmp_lt(uniform(0, 1), as_random(0.5))) INTO ev
    FROM (VALUES (8.0::float8), (10.0), (12.0)) AS t(x);
  RAISE NOTICE 'decline_boolean_factor_is_ok: %',
    (abs(expected(mu, ev) - 30.0/3.01) < 0.05);

  -- (d) two distinct latents coupled by one evidence set: joint posterior,
  -- out of the one-spec carrier; IS answers (a's posterior mean given one
  -- observation of a at 8 is 800/101, unaffected by b's observation).
  SELECT provenance_times(observe(normal(a, 1), 8),
                          observe(normal(b, 1), 9)) INTO ev;
  RAISE NOTICE 'decline_two_latents_is_ok: %',
    (abs(expected(a, ev) - 800.0/101.0) < 0.1);

  -- (e) nonzero literal lower bound of a uniform: not Pareto-conjugate
  -- (the 1/(theta - 1) kernel is not Pareto-shaped in theta itself); IS
  -- answers.  The Pareto(2, 3) prior keeps theta >= 2 > 1, so the leaf's
  -- parameters stay in-domain on every prior draw.
  ev := observe(uniform(1, theta2), 1.5);
  RAISE NOTICE 'decline_nonzero_uniform_lower_is_ok: %',
    (expected(theta2, ev) > 2.0 AND expected(theta2, ev) < 3.0);
END $$;

RESET provsql.rv_mc_samples;
RESET provsql.monte_carlo_seed;
RESET provsql.ess_warn_fraction;
