\set ECHO none \pset format unaligned -- Nonlinear transforms over random_variable (§B.3): the ^ operator and -- pow / power / ln / exp / sqrt, as three appended gate_arith opcodes -- (POW / LN / EXP; sqrt is pure sugar for ^ 0.5). Moments have no -- linearity to push through a nonlinear map, so evaluation is MC; -- constant subtrees still fold exactly, RangeCheck propagates sound -- support intervals through the transforms, and out-of-domain draws -- raise actionable errors instead of being silently dropped. -- (1) Constant folding is exact and needs no MC budget at all. SET provsql.rv_mc_samples = 0; SELECT provsql.expected(provsql.exp(provsql.as_random(0))) = 1 AS exp_zero_folds; SELECT abs(provsql.expected(provsql.exp(provsql.as_random(1))) - exp(1.0)) < 1e-15 AS exp_one_folds; SELECT provsql.expected(provsql.ln(provsql.as_random(1))) = 0 AS ln_one_folds; SELECT provsql.expected(provsql.pow(provsql.as_random(2), 3)) = 8 AS pow_two_cubed_folds; SELECT provsql.expected(provsql.as_random(9) ^ 0.5) = 3 AS sqrt_via_operator_folds; SELECT provsql.expected(provsql.sqrt(provsql.as_random(2.25))) = 1.5 AS sqrt_sugar_folds; -- A domain-violating constant does NOT fold (the NaN-as-sentinel -- convention keeps the gate intact); with the MC fallback disabled the -- evaluator reports the missing decomposition rather than a NaN. \set VERBOSITY terse SELECT provsql.expected(provsql.ln(provsql.as_random(-1))); \set VERBOSITY default RESET provsql.rv_mc_samples; -- (2) RangeCheck: sound support propagation through the transforms. SELECT lo, hi FROM support(provsql.exp(provsql.normal(0, 1))); -- [0, inf) -- Round hi: ln(10) prints 15 sig digits on PG<12 vs shortest-round-trip on PG>=12. SELECT lo, round(hi::numeric, 6) AS hi FROM support(provsql.ln(provsql.uniform(1, 10))); -- [0, ln 10] SELECT lo, hi FROM support(provsql.sqrt(provsql.uniform(0, 4))); -- [0, 2] SELECT lo, hi FROM support(provsql.uniform(1, 2) ^ provsql.normal(0, 1)); -- [0, inf) -- (3) The support bound decides comparisons without sampling: -- exp(X) >= 0 always, so P(exp(X) > -1) = 1 exactly. SELECT provsql.probability_evaluate( provsql.rv_cmp_gt(provsql.exp(provsql.normal(0, 1)), (-1)::random_variable), 'independent') = 1.0 AS exp_positive_exact; -- (4) Monte Carlo evaluation of the transforms (seeded). SET provsql.monte_carlo_seed = 42; -- E[exp(Z)] = e^0.5 (lognormal mean). SELECT abs(provsql.expected(provsql.exp(provsql.normal(0, 1))) - 1.6487212707001282) < 0.1 AS lognormal_mean_mc; -- E[ln U] = -1 for U(0,1). SELECT abs(provsql.expected(provsql.ln(provsql.uniform(0, 1))) + 1) < 0.05 AS ln_uniform_mean_mc; -- E[sqrt(U)] = 2/3. SELECT abs(provsql.expected(provsql.sqrt(provsql.uniform(0, 1))) - 2.0 / 3) < 0.02 AS sqrt_uniform_mean_mc; -- The motivating inverse-CDF construction (lessons §4): Y = 2·U^(1/4), -- E[Y] = 2/(1 + 1/4) = 1.6. SELECT abs(provsql.expected(2 * provsql.uniform(0, 1) ^ 0.25) - 1.6) < 0.02 AS generative_pow_mc; -- Median of exp(Z) is 1 (lognormal median = e^μ). WITH r AS (SELECT provsql.normal(0, 1) AS x) SELECT abs(provsql.quantile(provsql.exp(x), 0.5) - 1) < 0.05 AS lognormal_median_mc FROM r; -- Monotone-threshold agreement: P(exp(Z) > e) = 1 - Φ(1). SELECT abs(provsql.probability_evaluate( provsql.rv_cmp_gt(provsql.exp(provsql.normal(0, 1)), exp(1.0)::random_variable), 'monte-carlo', '100000') - 0.15865525393145707) < 0.01 AS lognormal_tail_mc; -- (5) Out-of-domain draws raise actionable errors (never a silently -- dropped NaN, which would bias the estimate). -- Assert the error class/prefix, not the offending draw: the exact draw is -- RNG-stream-dependent and differs across C++ stdlibs (libstdc++ vs libc++). \set VERBOSITY terse DO $$ BEGIN PERFORM provsql.expected(provsql.ln(provsql.normal(0, 1))); RAISE EXCEPTION 'expected an ln domain error, none was raised'; EXCEPTION WHEN others THEN IF SQLERRM LIKE '%ln: negative draw%' THEN RAISE NOTICE 'ln_domain_error_raised'; ELSE RAISE; END IF; END $$; DO $$ BEGIN PERFORM provsql.expected(provsql.sqrt(provsql.normal(0, 1))); RAISE EXCEPTION 'expected a sqrt/pow domain error, none was raised'; EXCEPTION WHEN others THEN IF SQLERRM LIKE '%pow: negative base drawn%' THEN RAISE NOTICE 'sqrt_domain_error_raised'; ELSE RAISE; END IF; END $$; \set VERBOSITY default -- The hinted rewrite works: E[sqrt(greatest(Z, 0))] ≈ 0.41108947933. SELECT abs(provsql.expected(provsql.sqrt(provsql.greatest( provsql.normal(0, 1), provsql.as_random(0)))) - 0.4110894793312293) < 0.02 AS sqrt_clamped_mc; RESET provsql.monte_carlo_seed; -- (6) Integer exponents are total: negative bases are fine. SET provsql.monte_carlo_seed = 42; -- E[Z^2] = 1 via pow (matches moment(x, 2)). SELECT abs(provsql.expected(provsql.normal(0, 1) ^ 2) - 1) < 0.05 AS integer_pow_total_mc; RESET provsql.monte_carlo_seed; -- (7) Unqualified spellings: exp / ln / sqrt / pow / power shadow -- pg_catalog names, so resolution must pick the rv overload for an rv -- argument (no rv -> float8 cast exists, so only the provsql candidate -- matches) while plain numeric arguments keep resolving to the -- pg_catalog functions (float8 is the preferred numeric type, so the -- implicit numeric -> rv cast never hijacks them). WITH r AS (SELECT provsql.uniform(0, 1) AS u) SELECT pg_typeof(exp(u)) = 'provsql.random_variable'::regtype AS exp_unqualified, pg_typeof(ln(u)) = 'provsql.random_variable'::regtype AS ln_unqualified, pg_typeof(sqrt(u)) = 'provsql.random_variable'::regtype AS sqrt_unqualified, pg_typeof(pow(u, 0.5)) = 'provsql.random_variable'::regtype AS pow_unqualified, pg_typeof(power(u, 2)) = 'provsql.random_variable'::regtype AS power_unqualified, pg_typeof(u ^ 0.5) = 'provsql.random_variable'::regtype AS caret_unqualified FROM r; SELECT pg_typeof(exp(1)) = 'double precision'::regtype AS exp_numeric_unaffected, pg_typeof(sqrt(2)) = 'double precision'::regtype AS sqrt_numeric_unaffected, pg_typeof(ln(2.0)) = 'numeric'::regtype AS ln_numeric_unaffected, pg_typeof(pow(2, 3)) = 'double precision'::regtype AS pow_numeric_unaffected, pg_typeof(2 ^ 3) = 'double precision'::regtype AS caret_numeric_unaffected; -- ...and the unqualified rv spellings evaluate correctly (exact -- constant folds, no MC budget needed). SET provsql.rv_mc_samples = 0; SELECT provsql.expected(sqrt(2.25::random_variable)) = 1.5 AS sqrt_unqualified_folds, provsql.expected(exp(0::random_variable)) = 1 AS exp_unqualified_folds, provsql.expected(pow(2::random_variable, 3)) = 8 AS pow_unqualified_folds; RESET provsql.rv_mc_samples; SELECT 'ok'::text AS continuous_transforms_done;