\set ECHO none \pset format unaligned -- Gamma(k, λ) with general (non-integer) shape k: the first family -- added under the per-family src/distributions/ layout. The SQL -- constructor routes integer shapes through erlang (the gamma with -- integer shape IS Erlang), so a gamma gate always carries a -- non-integer shape; its CDF is the regularised lower incomplete -- gamma P(k, λx). chi_squared(k) is sugar for gamma(k/2, 1/2). -- (1) Closed-form moments: -- E[Gamma(2.5, 0.4)] = k/λ = 6.25, Var = k/λ² = 15.625, -- E[X²] = k(k+1)/λ² = 2.5·3.5/0.16 = 54.6875 (tolerance-checked: -- 0.4² is not an exact double). SELECT provsql.expected(provsql.gamma(2.5, 0.4)) = 6.25 AS gamma_mean_exact; SELECT abs(provsql.variance(provsql.gamma(2.5, 0.4)) - 15.625) < 1e-12 AS gamma_var_exact; SELECT abs(provsql.moment(provsql.gamma(2.5, 0.4), 2) - 54.6875) < 1e-10 AS gamma_m2_exact; -- (2) Support: [0, +Infinity). SELECT lo, hi FROM support(provsql.gamma(2.5, 0.4)); -- (3) CDF, series branch of the incomplete gamma (λc < k+1): -- Gamma(0.5, 0.5) is χ²₁ = Z² for Z ~ N(0,1), so -- P(X <= 1) = P(|Z| <= 1) = erf(1/√2) ≈ 0.6826894921370859. SELECT abs(provsql.probability_evaluate( provsql.rv_cmp_le(provsql.gamma(0.5, 0.5), 1::random_variable), 'independent') - 0.6826894921370859) < 1e-12 AS gamma_cdf_series_exact; -- (4) CDF, continued-fraction branch (λc >= k+1): -- P(χ²₁ > 4) = P(|Z| > 2) = 1 - erf(√2) ≈ 0.04550026389635842. SELECT abs(provsql.probability_evaluate( provsql.rv_cmp_gt(provsql.gamma(0.5, 0.5), 4::random_variable), 'independent') - 0.04550026389635842) < 1e-12 AS gamma_cdf_cf_exact; -- (5) chi_squared sugar, odd df: chi_squared(1) = gamma(0.5, 0.5). SELECT abs(provsql.probability_evaluate( provsql.rv_cmp_le(provsql.chi_squared(1), 1::random_variable), 'independent') - 0.6826894921370859) < 1e-12 AS chisq1_sugar_exact; -- (6) chi_squared sugar, even df routes through erlang: -- chi_squared(4) = gamma(2, 0.5) = erlang(2, 0.5), and -- P(χ²₄ > 2) = e^{-1}(1 + 1) = 2/e via the Erlang finite-sum CDF. SELECT abs(provsql.probability_evaluate( provsql.rv_cmp_gt(provsql.chi_squared(4), 2::random_variable), 'independent') - 2.0 * exp(-1.0)) < 1e-12 AS chisq4_via_erlang_exact; -- (7) Integer-shape routing: gamma(3, 2) is stored as erlang:3,2, so -- its P(X > 1) matches erlang(3, 2)'s finite-sum CDF exactly. SELECT provsql.probability_evaluate( provsql.rv_cmp_gt(provsql.gamma(3, 2), 1::random_variable), 'independent') = provsql.probability_evaluate( provsql.rv_cmp_gt(provsql.erlang(3, 2), 1::random_variable), 'independent') AS gamma_integer_routes_to_erlang; -- (8) Same-rate sum closure: Gamma(0.5, 2) + Gamma(0.7, 2) folds to -- Gamma(1.2, 2) in the simplifier, so the comparison resolves via the -- closed-form CDF (exact, no MC noise): -- P(Gamma(1.2, 2) <= 1) = P(1.2, 2) ≈ 0.8176987670910338 (mpmath). SELECT abs(provsql.probability_evaluate( provsql.rv_cmp_le(provsql.gamma(0.5, 2) + provsql.gamma(0.7, 2), 1::random_variable), 'independent') - 0.8176987670910338) < 1e-12 AS gamma_sum_closure_exact; -- (9) Positive-scaling fold: 2 · Gamma(0.5, 1) = Gamma(0.5, 0.5) = χ²₁, -- so P(2X <= 1) = erf(1/√2) again, exactly, via the affine fold + CDF. SELECT abs(provsql.probability_evaluate( provsql.rv_cmp_le(2 * provsql.gamma(0.5, 1), 1::random_variable), 'independent') - 0.6826894921370859) < 1e-12 AS gamma_scale_fold_exact; -- (10) Gamma vs Gamma comparison: the pdf declines at the shape<1 -- singular endpoint, so the pairwise quadrature bails and the whole -- circuit falls to seeded MC sampling std::gamma_distribution. -- P(X < Y) for X ~ Gamma(0.5, 1), Y ~ Gamma(0.7, 1): X/(X+Y) ~ -- Beta(0.5, 0.7), so P = I_{1/2}(0.5, 0.7) ≈ 0.6003642321330015. SET provsql.monte_carlo_seed = 42; SELECT abs(provsql.probability_evaluate( provsql.rv_cmp_lt(provsql.gamma(0.5, 1), provsql.gamma(0.7, 1)), 'monte-carlo', '100000') - 0.6003642321330015) < 0.01 AS gamma_vs_gamma_mc_within_tolerance; RESET provsql.monte_carlo_seed; -- (11) Constructor validation errors. VERBOSITY terse keeps the -- plpgsql CONTEXT lines out of the expected output. \set VERBOSITY terse SELECT provsql.gamma(0, 1); SELECT provsql.gamma(1, 0); SELECT provsql.gamma('NaN'::double precision, 1); SELECT provsql.chi_squared(-1); \set VERBOSITY default -- (12) End-to-end through the planner hook: WHERE rv > c on a -- gamma-valued column resolves to the exact CDF answer. CREATE TABLE gamma_sensors(id text, load provsql.random_variable); INSERT INTO gamma_sensors VALUES ('a', provsql.gamma(0.5, 0.5)), ('b', provsql.chi_squared(4)); SELECT add_provenance('gamma_sensors'); CREATE TABLE gamma_result AS SELECT id, probability_evaluate(provenance(), 'independent') AS p FROM gamma_sensors WHERE load > 2; SELECT remove_provenance('gamma_result'); -- P(χ²₁ > 2) = P(|Z| > √2) = 1 - erf(1) ≈ 0.1572992070502851; -- P(χ²₄ > 2) = 2/e (erlang finite sum). SELECT id, abs(p - CASE id WHEN 'a' THEN 1 - 0.8427007929497149 WHEN 'b' THEN 2.0 * exp(-1.0) END) < 1e-12 AS within_tolerance FROM gamma_result ORDER BY id; DROP TABLE gamma_result; DROP TABLE gamma_sensors; SELECT 'ok'::text AS continuous_gamma_done;