\set ECHO none
\pset format unaligned

-- The 'd-tree' method (src/DTree) refines the cheap leaf bound by independent
-- decomposition and Shannon expansion: a deterministic anytime engine that is
-- EXACT when run to a zero-width interval (the default, by name) and certified
-- additive when given an epsilon.  It applies to ANY Boolean circuit -- a
-- monotone DNF takes an optimised clause path, while nested AND/OR (CNF) and
-- negation (EXCEPT / monus, A AND NOT B) take the general circuit recursion
-- (dtreeBoundsCircuit).  We assert invariants (equals the exact baselines;
-- within eps of exact) rather than brittle float values.
SET provsql.provenance = 'semiring';

CREATE TABLE dt(id int);
INSERT INTO dt SELECT generate_series(1,8);
SELECT add_provenance('dt');
DO $$ BEGIN PERFORM set_prob(provenance(), 0.1*id) FROM dt; END $$;

SET provsql.active = off;
DO $$
DECLARE x uuid[];
BEGIN
  SELECT array_agg(provsql::uuid ORDER BY id) INTO x FROM dt;
  -- Entangled (non-read-once) DNF: a 4-cycle (x1 x2)∨(x2 x3)∨(x3 x4)∨(x1 x4).
  -- Every variable is shared across clauses, so the leaf bound is loose and the
  -- engine must Shannon-expand to reach the exact value.
  PERFORM set_config('dt.ent', provenance_plus(ARRAY[
     provenance_times(x[1],x[2]), provenance_times(x[2],x[3]),
     provenance_times(x[3],x[4]), provenance_times(x[1],x[4])])::text, false);
  -- Read-once DNF (x1 x2)∨(x3 x4): disjoint, so the bound is already exact.
  PERFORM set_config('dt.ro', provenance_plus(ARRAY[
     provenance_times(x[1],x[2]), provenance_times(x[3],x[4])])::text, false);
  -- Non-DNF: AND-of-ORs (x1∨x2)(x3∨x4) -- handled by the general recursion.
  PERFORM set_config('dt.cnf', provenance_times(
     provenance_plus(ARRAY[x[1],x[2]]),
     provenance_plus(ARRAY[x[3],x[4]]))::text, false);
  -- Non-monotone: ((x1∨x2) EXCEPT x3) ∨ (x4 AND NOT x5), i.e. negation via monus
  -- (A monus B = A AND NOT B), the case Phase 1 could not handle at all.
  PERFORM set_config('dt.neg', provenance_plus(ARRAY[
     provenance_monus(provenance_plus(ARRAY[x[1],x[2]]), x[3]),
     provenance_monus(x[4], x[5])])::text, false);
  -- 8-cycle (treewidth 2, 8 clauses): the Shannon recursion reaches the same
  -- residual sub-paths along different branches, so this exercises the memoised
  -- shared-DAG path (Phase 2) and must still agree with the exact baseline.
  PERFORM set_config('dt.cyc', provenance_plus(ARRAY[
     provenance_times(x[1],x[2]), provenance_times(x[2],x[3]),
     provenance_times(x[3],x[4]), provenance_times(x[4],x[5]),
     provenance_times(x[5],x[6]), provenance_times(x[6],x[7]),
     provenance_times(x[7],x[8]), provenance_times(x[8],x[1])])::text, false);
END $$;
RESET provsql.active;

-- Exact d-tree matches the exact baselines (possible-worlds, sieve) on the
-- entangled circuit, and on the read-once one.
SELECT abs(probability_evaluate(current_setting('dt.ent')::uuid,'d-tree')
         - probability_evaluate(current_setting('dt.ent')::uuid,'possible-worlds'))
         < 1e-9 AS ent_eq_pw,
       abs(probability_evaluate(current_setting('dt.ent')::uuid,'d-tree')
         - probability_evaluate(current_setting('dt.ent')::uuid,'sieve'))
         < 1e-9 AS ent_eq_sieve,
       abs(probability_evaluate(current_setting('dt.ro')::uuid,'d-tree')
         - probability_evaluate(current_setting('dt.ro')::uuid,'possible-worlds'))
         < 1e-9 AS ro_eq_pw;

-- Memoised shared-DAG recursion (8-cycle) agrees with the exact baseline.
SELECT abs(probability_evaluate(current_setting('dt.cyc')::uuid,'d-tree')
         - probability_evaluate(current_setting('dt.cyc')::uuid,'tree-decomposition'))
         < 1e-9 AS cyc_eq_td;

-- Additive (by-name epsilon): the estimate is within eps of the exact value.
SELECT abs(probability_evaluate(current_setting('dt.ent')::uuid,'d-tree','epsilon=0.05')
         - probability_evaluate(current_setting('dt.ent')::uuid,'possible-worlds'))
         <= 0.05 AS add_within_eps;

-- Non-DNF circuits (a CNF, and a non-monotone one with negation) are now handled
-- by the general circuit recursion, exactly matching the possible-worlds baseline.
SELECT abs(probability_evaluate(current_setting('dt.cnf')::uuid,'d-tree')
         - probability_evaluate(current_setting('dt.cnf')::uuid,'possible-worlds'))
         < 1e-9 AS cnf_eq_pw,
       abs(probability_evaluate(current_setting('dt.neg')::uuid,'d-tree')
         - probability_evaluate(current_setting('dt.neg')::uuid,'possible-worlds'))
         < 1e-9 AS neg_eq_pw,
       abs(probability_evaluate(current_setting('dt.neg')::uuid,'d-tree','epsilon=0.05')
         - probability_evaluate(current_setting('dt.neg')::uuid,'possible-worlds'))
         <= 0.05 AS neg_add_within_eps;

-- Deterministic (delta=0) admissibility: an 'additive' request with delta=0
-- excludes the (eps,delta) samplers, so the chooser settles on a DETERMINISTIC
-- method (an exact one when cheap, else the d-tree) and the value stays within
-- eps of exact.  We assert the route is not a sampler and the value is sound.
SET provsql.last_eval_method = '';
SELECT abs(probability_evaluate(current_setting('dt.ent')::uuid,'additive','epsilon=0.1,delta=0')
         - probability_evaluate(current_setting('dt.ent')::uuid,'possible-worlds'))
         <= 0.1 AS det_within_eps;
SELECT current_setting('provsql.last_eval_method')
         !~ '(monte-carlo|karp-luby|stopping-rule)' AS det_not_sampler;

-- A by-name sampler cannot honour delta=0 (infinite sample count): clean error.
SELECT probability_evaluate(current_setting('dt.ent')::uuid,'karp-luby','epsilon=0.1,delta=0');

-- Speculative-execution budget.  In the default chain the chooser budgets the
-- d-tree at the next-best method's cost and silently escalates on overrun (see
-- probability_paths' CNF case); the debug GUC provsql.dtree_max_subproblems
-- imposes a hard subproblem cap.  A by-name call has no chooser to escalate to,
-- so an overrun surfaces as a clean error.  cap=1 forces it on the entangled
-- circuit (which needs more than one subproblem); unset, the same call succeeds.
SET provsql.dtree_max_subproblems = 1;
SELECT probability_evaluate(current_setting('dt.ent')::uuid,'d-tree');
RESET provsql.dtree_max_subproblems;
SELECT probability_evaluate(current_setting('dt.ent')::uuid,'d-tree') > 0 AS dtree_runs_unbudgeted;

SELECT remove_provenance('dt');
DROP TABLE dt;
RESET provsql.provenance;

-- BID / multivalued (gate_mulinput) circuits.  repair_key builds mutually
-- exclusive blocks; the general d-tree recursion once THREW on a mulinput gate.
-- The dispatcher now rewrites multivalued blocks to independent Booleans before
-- any method whose handlesMultivalued() is false (every method except
-- 'independent'), so 'd-tree' evaluates BID circuits and agrees with the exact
-- baseline instead of bailing.
CREATE TABLE dtb(k int, v int);
INSERT INTO dtb VALUES (1,10),(1,11),(2,20),(2,21),(2,22);
SELECT repair_key('dtb','k');
DO $$ BEGIN PERFORM set_prob(provenance(),
  (CASE v WHEN 10 THEN 0.2 WHEN 11 THEN 0.3 WHEN 20 THEN 0.1
          WHEN 21 THEN 0.4 ELSE 0.25 END)::double precision) FROM dtb; END $$;

SET provsql.active = off;
DO $$
BEGIN
  -- a bare mulinput alternative, a cross-block conjunction, and an intra-block
  -- (mutually exclusive) disjunction
  PERFORM set_config('dtb.single', (SELECT provsql FROM dtb WHERE v=11)::text, false);
  PERFORM set_config('dtb.conj', provenance_times(
     (SELECT provsql FROM dtb WHERE v=10),
     (SELECT provsql FROM dtb WHERE v=21))::text, false);
  PERFORM set_config('dtb.disj', provenance_plus(ARRAY[
     (SELECT provsql FROM dtb WHERE v=20),
     (SELECT provsql FROM dtb WHERE v=21)])::text, false);
END $$;
RESET provsql.active;

SELECT abs(probability_evaluate(current_setting('dtb.single')::uuid,'d-tree')
         - probability_evaluate(current_setting('dtb.single')::uuid,'possible-worlds'))
         < 1e-9 AS bid_single_eq_pw,
       abs(probability_evaluate(current_setting('dtb.conj')::uuid,'d-tree')
         - probability_evaluate(current_setting('dtb.conj')::uuid,'possible-worlds'))
         < 1e-9 AS bid_conj_eq_pw,
       abs(probability_evaluate(current_setting('dtb.disj')::uuid,'d-tree')
         - probability_evaluate(current_setting('dtb.disj')::uuid,'possible-worlds'))
         < 1e-9 AS bid_disj_eq_pw;

SELECT remove_provenance('dtb');
DROP TABLE dtb;
