\set ECHO none -- Regression coverage for foldBooleanIdentities (the load-time -- Boolean-only simplifier gated on provsql.boolean_provenance). -- -- The rules : -- B1 (idempotence) : gate_plus(a, a, b) -> gate_plus(a, b) -- gate_times(a, a, b) -> gate_times(a, b) -- B2 (plus-with-one absorber) : gate_plus(..., gate_one, ...) -> gate_one -- -- Each rule application wraps the result in gate_assumed_boolean so -- semiring evaluators incompatible with the Boolean assumption refuse. \pset format unaligned SET search_path TO public, provsql; CREATE TABLE bf_t(id int, lbl text); INSERT INTO bf_t VALUES (1, 'u'), (2, 'v'); SELECT add_provenance('bf_t'); DO $$ BEGIN PERFORM set_prob(provsql, 0.5) FROM bf_t; END $$; -- Numeric mapping used to drive sr_counting refusal (the only way to -- exercise a non-Boolean-compatible semiring against the wrapped -- root, since sr_* are STRICT on the mapping argument). CREATE TABLE bf_t_cnt AS SELECT 1::int AS value, provsql AS provenance FROM bf_t; -- ---------------------------------------------------------------------- -- (1) Idempotence on a SHARED INTERIOR subexpression unlocks -- independent on a circuit that is otherwise not read-once. -- times(plus(u, v), plus(u, v)) has the same plus(u, v) subgate -- reached from the root through two distinct paths -- independent's -- read-once check rejects it. With boolean_provenance on, B1 -- dedups the times wires to a single plus(u, v), the trailing -- semiring-safe pass substitutes single-wire times -> plus(u, v), -- and independent succeeds with the read-once OR probability -- 1 - (1 - 0.5)*(1 - 0.5) = 0.75. -- ---------------------------------------------------------------------- DO $$ DECLARE u uuid; v uuid; sub uuid; root uuid; BEGIN SELECT provsql INTO u FROM bf_t WHERE id = 1; SELECT provsql INTO v FROM bf_t WHERE id = 2; sub := provenance_plus(ARRAY[u, v]); root := uuid_generate_v5(uuid_ns_provsql(), concat('bf-tested-shared-sub', sub)); PERFORM create_gate(root, 'times', ARRAY[sub, sub]); PERFORM set_config('bf.shared_root', root::text, false); END $$; -- Without folding, independent refuses (not read-once). SET provsql.boolean_provenance = off; DO $$ DECLARE raised boolean := false; BEGIN BEGIN PERFORM probability_evaluate( current_setting('bf.shared_root')::uuid, 'independent'); EXCEPTION WHEN OTHERS THEN raised := true; END; IF NOT raised THEN RAISE EXCEPTION 'expected ''independent'' to refuse the non-read-once ' 'circuit with boolean_provenance = off'; END IF; END $$; -- With folding, idempotence collapses the duplicate plus(u, v) child, -- the in-memory boolean_assumed flag fires on the root, and -- independent yields the read-once probability. SET provsql.boolean_provenance = on; SELECT round(probability_evaluate( current_setting('bf.shared_root')::uuid, 'independent') ::numeric, 9) AS shared_root_p_ind; -- simplified_circuit_subgraph exposes the in-memory folded form ; -- the root keeps its original gate type (no wrapper minted) and -- carries the boolean_assumed flag set by foldBooleanIdentities. SELECT (simplified_circuit_subgraph( current_setting('bf.shared_root')::uuid, 1) ->0->>'gate_type') AS shared_root_simplified_type, (simplified_circuit_subgraph( current_setting('bf.shared_root')::uuid, 1) ->0->>'boolean_assumed') AS shared_root_boolean_assumed; -- sr_counting refuses on the wrapped circuit : counting is not -- compatible with the Boolean assumption. DO $$ DECLARE raised boolean := false; BEGIN BEGIN PERFORM sr_counting( current_setting('bf.shared_root')::uuid, 'bf_t_cnt'::regclass); EXCEPTION WHEN OTHERS THEN raised := true; END; IF NOT raised THEN RAISE EXCEPTION 'expected sr_counting to refuse a Boolean-wrapped circuit'; END IF; END $$; SET provsql.boolean_provenance = off; -- ---------------------------------------------------------------------- -- (2) Plus-with-one absorber. gate_plus(u, gate_one()) collapses to -- gate_one once boolean_provenance is on ; the gate keeps its -- UUID, foldSemiringIdentities mutates its type to gate_one, and -- the boolean_assumed flag is set so non-Boolean-compatible -- semirings refuse. -- ---------------------------------------------------------------------- DO $$ DECLARE u uuid; root uuid; BEGIN SELECT provsql INTO u FROM bf_t WHERE id = 1; root := uuid_generate_v5(uuid_ns_provsql(), concat('bf-one-abs', u)); PERFORM create_gate(root, 'plus', ARRAY[u, gate_one()]); PERFORM set_config('bf.one_root', root::text, false); END $$; SET provsql.boolean_provenance = on; SELECT (simplified_circuit_subgraph( current_setting('bf.one_root')::uuid, 2) ->0->>'gate_type') AS one_root_simplified_type, (simplified_circuit_subgraph( current_setting('bf.one_root')::uuid, 2) ->0->>'boolean_assumed') AS one_root_boolean_assumed; -- After folding the wrapper sits over a gate_one ; probability is 1. SELECT round(probability_evaluate( current_setting('bf.one_root')::uuid, 'independent') ::numeric, 9) AS one_root_p_ind; SET provsql.boolean_provenance = off; -- ---------------------------------------------------------------------- -- (3) Phase 3 of foldSemiringIdentities, when chained after the -- Boolean dedup, substitutes single-wire plus / times in place. -- Pre-fix, that substitution copied target type / wires / infos -- / extra but not the target's probability nor its membership in -- the inputs set : a substituted gate that landed as a -- gate_input with prob 0.5 instead read as an input with the -- addGate-default prob 1. -- -- Probe : gate_plus(u, gate_zero()) collapses to u via -- Phase 1 (drop the zero wire) then Phase 2 / Phase 3 -- (singleton substitute). u has prob 0.5 ; the probability -- evaluated on the root must equal 0.5, not 1. -- ---------------------------------------------------------------------- DO $$ DECLARE u uuid; root uuid; BEGIN SELECT provsql INTO u FROM bf_t WHERE id = 1; root := uuid_generate_v5(uuid_ns_provsql(), concat('bf-zero', u)); PERFORM create_gate(root, 'plus', ARRAY[u, gate_zero()]); PERFORM set_config('bf.zero_root', root::text, false); END $$; SELECT round(probability_evaluate( current_setting('bf.zero_root')::uuid, 'independent') ::numeric, 9) AS zero_root_p_ind; -- ---------------------------------------------------------------------- -- (4) Absorption (B3, Boolean-only). -- gate_plus(x, gate_times(x, y)) -> gate_plus(x) -> x -- gate_times(x, gate_plus(x, y)) -> gate_times(x) -> x -- The absorbed times / plus child is dominated by sibling x. -- Sound under Boolean algebra (x OR (x AND y) = x ; -- x AND (x OR y) = x) but unsound in general semirings, so the -- parent is marked boolean_assumed. -- ---------------------------------------------------------------------- DO $$ DECLARE u uuid; v uuid; tab uuid; root_plus uuid; root_times uuid; BEGIN SELECT provsql INTO u FROM bf_t WHERE id = 1; SELECT provsql INTO v FROM bf_t WHERE id = 2; -- plus(u, times(u, v)) absorbs to u. P(u) = 0.5. tab := provenance_times(u, v); root_plus := uuid_generate_v5(uuid_ns_provsql(), concat('bf-abs-plus', tab)); PERFORM create_gate(root_plus, 'plus', ARRAY[u, tab]); PERFORM set_config('bf.abs_plus_root', root_plus::text, false); -- times(u, plus(u, v)) absorbs to u. P(u) = 0.5. tab := provenance_plus(ARRAY[u, v]); root_times := uuid_generate_v5(uuid_ns_provsql(), concat('bf-abs-times', tab)); PERFORM create_gate(root_times, 'times', ARRAY[u, tab]); PERFORM set_config('bf.abs_times_root', root_times::text, false); END $$; SET provsql.boolean_provenance = on; SELECT round(probability_evaluate( current_setting('bf.abs_plus_root')::uuid, 'independent') ::numeric, 9) AS plus_abs_p_ind; SELECT round(probability_evaluate( current_setting('bf.abs_times_root')::uuid, 'independent') ::numeric, 9) AS times_abs_p_ind; SET provsql.boolean_provenance = off; -- ---------------------------------------------------------------------- -- (5) Real-world absorption pattern : the UNION's first branch -- dominates pairs from the second branch. -- -- The query : -- SELECT city FROM personnel -- UNION -- SELECT p1.city FROM personnel p1, personnel p2 -- WHERE p1.city = p2.city AND p1.name < p2.name -- -- Branch 1 yields one provenance leaf per personnel row. -- Branch 2 yields gate_times(p1, p2) for every pair sharing a -- city. In the per-city plus, every gate_times(p1, p2) is -- dominated by p1 (and p2) sitting alongside it in branch 1 : -- absorption collapses every times-pair, leaving a per-city OR -- over the branch-1 leaves alone. -- -- This is unsound in Counting / Tropical / etc. but exact in -- Boolean ; with provsql.boolean_provenance = on the circuit -- becomes read-once and 'independent' evaluates it directly. -- Without the GUC, independent throws "Not an independent -- circuit" on the shared p1 / p2 leaves. -- ---------------------------------------------------------------------- CREATE TABLE bf_personnel(id int, name text, city text); INSERT INTO bf_personnel VALUES (1, 'Alice', 'Paris'), (2, 'Bob', 'Paris'), (3, 'Carol', 'Paris'), (4, 'Dave', 'Berlin'), (5, 'Ellen', 'Berlin'); SELECT add_provenance('bf_personnel'); DO $$ BEGIN PERFORM set_prob(provsql, 0.6) FROM bf_personnel; END $$; -- Without absorption (and the rewriter), 'independent' fails on the -- shared p1 / p2 leaves. SET provsql.boolean_provenance = off; DO $$ DECLARE raised boolean := false; BEGIN BEGIN PERFORM probability_evaluate(provenance(), 'independent') FROM ( SELECT city FROM bf_personnel UNION SELECT p1.city FROM bf_personnel p1, bf_personnel p2 WHERE p1.city = p2.city AND p1.name < p2.name ) t; EXCEPTION WHEN OTHERS THEN raised := true; END; IF NOT raised THEN RAISE EXCEPTION 'expected ''independent'' to refuse the unrewritten ' 'union with boolean_provenance = off'; END IF; END $$; -- With absorption, the circuit becomes read-once and independent -- yields exact per-city probabilities. Berlin has 2 people, prob -- 0.6 each : P = 1 - (1 - 0.6)^2 = 0.84. Paris has 3, P = 1 - -- 0.4^3 = 0.936. SET provsql.boolean_provenance = on; CREATE TEMP TABLE bf_abs_demo AS SELECT city, probability_evaluate(provenance(), 'independent') AS p FROM ( SELECT city FROM bf_personnel UNION SELECT p1.city FROM bf_personnel p1, bf_personnel p2 WHERE p1.city = p2.city AND p1.name < p2.name ) t; SELECT remove_provenance('bf_abs_demo'); SELECT city, round(p::numeric, 6) AS p_independent FROM bf_abs_demo ORDER BY city; DROP TABLE bf_abs_demo; SET provsql.boolean_provenance = off; DROP TABLE bf_personnel; DROP TABLE bf_t_cnt; DROP TABLE bf_t;