\set ECHO none \pset format unaligned -- Latent variables, Part B: likelihood-weighting posterior inference. -- observe(X, d) binds a datum to a latent-dependent gate_rv leaf; and_agg -- conjoins the per-observation evidence; the moment / quantile / sample -- readouts, passed that evidence as their conditioning argument, report the -- posterior via self-normalised importance sampling. Seeded-MC tolerance -- checks against closed-form conjugate posteriors. SET search_path TO provsql, public; SET provsql.monte_carlo_seed = 20260705; SET provsql.rv_mc_samples = 200000; SET provsql.ess_warn_fraction = 0; -- silence ESS warnings except where tested -- --------------------------------------------------------------------- -- Normal-Normal conjugacy. Prior mu ~ N(0, 10); data x_i ~ N(mu, 1); -- data {8, 10, 12} (n = 3, sum = 30). Posterior: -- 1/tau_n^2 = 1/100 + 3/1 = 3.01 -> var = 0.33223, mean = 9.9668. -- --------------------------------------------------------------------- DO $$ DECLARE mu random_variable := normal(0, 10); ev uuid; m double precision; v 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); m := expected(mu, ev); v := variance(mu, ev); RAISE NOTICE 'normal_normal_mean: %', (abs(m - 9.9668) < 0.1); RAISE NOTICE 'normal_normal_var: %', (abs(v - 0.33223) < 0.05); -- Posterior median ~ posterior mean for a (symmetric) Gaussian posterior. RAISE NOTICE 'normal_normal_median: %', (abs(quantile(mu, 0.5, ev) - 9.9668) < 0.15); -- Marginal likelihood P(data): closed form is the N(0, I + 100*11^T) -- density at (8,10,12) = 4.0723e-5. RAISE NOTICE 'normal_normal_evidence: %', (abs(evidence(ev) / 4.0723e-5 - 1.0) < 0.05); END $$; -- --------------------------------------------------------------------- -- Gamma-Exponential conjugacy. Prior rate lambda ~ Gamma(shape 2, rate 1); -- data x_i ~ Exponential(lambda); data {0.5, 1.0, 1.5} (n = 3, sum = 3). -- Posterior lambda ~ Gamma(2 + 3, 1 + 3) = Gamma(5, 4): -- mean = 5/4 = 1.25, var = 5/16 = 0.3125. -- --------------------------------------------------------------------- DO $$ DECLARE lambda random_variable := gamma(2, 1); ev uuid; BEGIN SELECT and_agg(observe(exponential(lambda), x)) INTO ev FROM (VALUES (0.5::float8), (1.0), (1.5)) AS t(x); RAISE NOTICE 'gamma_exp_mean: %', (abs(expected(lambda, ev) - 1.25) < 0.06); RAISE NOTICE 'gamma_exp_var: %', (abs(variance(lambda, ev) - 0.3125) < 0.06); END $$; -- --------------------------------------------------------------------- -- Posterior predictive: rv_sample on a fresh leaf reusing the latent draws -- from the same evidence. The predictive mean tracks the posterior mean. -- --------------------------------------------------------------------- DO $$ DECLARE mu random_variable := normal(0, 10); ev uuid; pred_mean 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 avg(s) INTO pred_mean FROM rv_sample((normal(mu, 1))::uuid, 20000, ev) s; RAISE NOTICE 'posterior_predictive_mean: %', (abs(pred_mean - 9.9668) < 0.2); END $$; -- --------------------------------------------------------------------- -- Shapley attribution: with three tight observations and one outlier, the -- outlier gets the largest-magnitude posterior-mean attribution, and the -- values sum to the prior->posterior shift (Shapley efficiency). -- --------------------------------------------------------------------- DO $$ DECLARE mu random_variable := normal(0, 10); toks uuid[]; ev uuid; total double precision; outlier_shap double precision; max_other double precision; post_shift double precision; BEGIN -- Build the evidence with and_agg (a left-nested gate_times), and keep the -- atoms ordered by x so toks[4] is the outlier; shapley_observe recovers the -- flat atom set through the recursive observe_atoms collector. SELECT array_agg(o ORDER BY x), and_agg(o) INTO toks, ev FROM (SELECT x, observe(normal(mu, 1), x) AS o FROM (VALUES (5.0::float8), (5.5), (4.5), (30.0)) AS t(x)) s; -- toks[4] is the outlier (x = 30, largest under ORDER BY x). Compute the -- attribution once and reuse it. CREATE TEMP TABLE sh_result ON COMMIT DROP AS SELECT observation, value FROM shapley_observe((mu)::uuid, ev, 'expected'); SELECT value INTO outlier_shap FROM sh_result WHERE observation = toks[4]; SELECT max(abs(value)) INTO max_other FROM sh_result WHERE observation <> toks[4]; SELECT sum(value) INTO total FROM sh_result; post_shift := expected(mu, ev) - expected(mu, gate_one()); RAISE NOTICE 'shapley_outlier_dominates: %', (abs(outlier_shap) > max_other); RAISE NOTICE 'shapley_efficiency: %', (abs(total - post_shift) < 0.15); END $$; -- --------------------------------------------------------------------- -- ESS diagnostic: many tight observations over a broad prior degenerate the -- weights; with the default warn fraction a WARNING is emitted (its counts -- depend on the RNG stream, so it is suppressed here to keep the expected -- output platform-independent -- the low-ESS path is still exercised, and -- the posterior stays correct). The latent reaches the observed leaves -- through an arithmetic doubling so the conjugate recogniser declines and -- the evidence stays on the importance-sampling path this block tests: -- observations of 2·mu around 20 put mu's posterior at ~10. -- --------------------------------------------------------------------- SET provsql.ess_warn_fraction = 0.5; SET provsql.rv_mc_samples = 100000; DO $$ DECLARE mu random_variable := normal(0, 50); ev uuid; m double precision; BEGIN SELECT and_agg(observe(normal(mu + mu, 1), x)) INTO ev FROM (VALUES (20.0::float8), (20.1), (19.9), (20.05), (19.95), (20.0)) AS t(x); PERFORM set_config('client_min_messages', 'error', true); -- hide the WARNING m := expected(mu, ev); -- degenerate ESS: emits (suppressed) low-ESS WARNING PERFORM set_config('client_min_messages', 'notice', true); RAISE NOTICE 'ess_posterior_mean_ok: %', (abs(m - 10.0) < 0.25); END $$; -- --------------------------------------------------------------------- -- The equality-form surface: "X | (Y = c)" (single conditioning) and -- "given(Y = c)" (per-row evidence for and_agg) route a continuous point -- observation to likelihood weighting -- the same posterior as the internal -- observe(), but the natural conditional-equality syntax. -- --------------------------------------------------------------------- SET provsql.ess_warn_fraction = 0; SET provsql.rv_mc_samples = 300000; DO $$ DECLARE mu random_variable := normal(0, 10); ev uuid; BEGIN -- single continuous conditioning on a point observation matches observe() RAISE NOTICE 'continuous_equality_form: %', (abs(expected(mu | (normal(mu, 1) = 8)) - expected(mu, observe(normal(mu, 1), 8))) < 0.1); -- given(Y = c) aggregation reproduces the Normal-Normal posterior (9.9668) SELECT and_agg(given(normal(mu, 1) = x)) INTO ev FROM (VALUES (8.0::float8), (10.0), (12.0)) AS t(x); RAISE NOTICE 'given_aggregation_posterior: %', (abs(expected(mu, ev) - 9.9668) < 0.1); END $$;