checkpoint, some test pass
This commit is contained in:
parent
c51c35bfe4
commit
3ea8d80801
86
lib/til.ex
86
lib/til.ex
@ -1435,10 +1435,12 @@ defmodule Tdd.Algo do
|
|||||||
cache_key = {:negate, tdd_id}
|
cache_key = {:negate, tdd_id}
|
||||||
|
|
||||||
case Store.get_op_cache(cache_key) do
|
case Store.get_op_cache(cache_key) do
|
||||||
|
# If the result is already cached (or in progress), return it.
|
||||||
{:ok, result_id} ->
|
{:ok, result_id} ->
|
||||||
result_id
|
result_id
|
||||||
|
|
||||||
:not_found ->
|
:not_found ->
|
||||||
|
# On cache miss, compute the negation.
|
||||||
result_id =
|
result_id =
|
||||||
case Store.get_node(tdd_id) do
|
case Store.get_node(tdd_id) do
|
||||||
{:ok, :true_terminal} ->
|
{:ok, :true_terminal} ->
|
||||||
@ -1447,17 +1449,44 @@ defmodule Tdd.Algo do
|
|||||||
{:ok, :false_terminal} ->
|
{:ok, :false_terminal} ->
|
||||||
Store.true_node_id()
|
Store.true_node_id()
|
||||||
|
|
||||||
|
# This is the main recursive case for an internal node.
|
||||||
{:ok, {var, y, n, d}} ->
|
{:ok, {var, y, n, d}} ->
|
||||||
Store.find_or_create_node(var, negate(y), negate(n), negate(d))
|
# 1. Create a temporary placeholder and cache it immediately.
|
||||||
|
# This acts as a co-inductive "hook". Any recursive call to negate
|
||||||
|
# this same `tdd_id` will hit the cache and get this placeholder,
|
||||||
|
# breaking the infinite recursion.
|
||||||
|
placeholder = Store.create_placeholder({:negation_of, tdd_id})
|
||||||
|
Store.put_op_cache(cache_key, placeholder)
|
||||||
|
|
||||||
# TODO: Handle placeholder nodes encountered during co-inductive simplification.
|
# 2. Recursively negate the children.
|
||||||
# A placeholder is treated as `any` for the check, so its negation is `none`.
|
neg_y = negate(y)
|
||||||
|
neg_n = negate(n)
|
||||||
|
neg_d = negate(d)
|
||||||
|
|
||||||
|
# 3. Build the main body of the negated TDD. If there was a cycle,
|
||||||
|
# this structure will refer to the `placeholder` ID at its leaves.
|
||||||
|
body_id = Store.find_or_create_node(var, neg_y, neg_n, neg_d)
|
||||||
|
|
||||||
|
# 4. "Tie the knot": Replace any occurrences of the placeholder within the
|
||||||
|
# newly created graph with the ID of the graph's root (`body_id`).
|
||||||
|
# This correctly forms the cycle in the negated graph.
|
||||||
|
final_id = substitute(body_id, placeholder, body_id)
|
||||||
|
|
||||||
|
# 5. The canonical graph is now built. Update the cache with the
|
||||||
|
# final, correct ID before returning.
|
||||||
|
Store.put_op_cache(cache_key, final_id)
|
||||||
|
final_id
|
||||||
|
|
||||||
|
# This case indicates a logical error. A fully compiled TDD passed to `negate`
|
||||||
|
# should not be a placeholder itself. This new implementation avoids the
|
||||||
|
# previous incorrect handling.
|
||||||
{:ok, {:placeholder, _spec}} ->
|
{:ok, {:placeholder, _spec}} ->
|
||||||
IO.inspect("RETURNING FALSE FOR NEGATE PLACEHOLDER")
|
raise "Tdd.Algo.negate: Attempted to negate a raw placeholder node. This indicates a logic error in the calling algorithm."
|
||||||
Store.false_node_id()
|
|
||||||
|
{:error, reason} ->
|
||||||
|
raise "Tdd.Algo.negate: Failed to get node #{tdd_id}: #{reason}"
|
||||||
end
|
end
|
||||||
|
|
||||||
Store.put_op_cache(cache_key, result_id)
|
|
||||||
result_id
|
result_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -1779,6 +1808,24 @@ defmodule Tdd.Algo do
|
|||||||
# This is the coinductive context (a MapSet).
|
# This is the coinductive context (a MapSet).
|
||||||
context
|
context
|
||||||
) do
|
) do
|
||||||
|
# --- BEGIN IMPROVEMENT ---
|
||||||
|
# Co-inductive check for the constraint ID itself. If we are being asked to
|
||||||
|
# reason about a constraint that is a placeholder (i.e., part of a cycle
|
||||||
|
# currently being built), we must avoid operating on it directly. The correct
|
||||||
|
# co-inductive assumption is that the check succeeds, so we proceed down
|
||||||
|
# the 'yes' path without performing invalid logic on the placeholder.
|
||||||
|
case Store.get_node(constraint_id) do
|
||||||
|
{:ok, {:placeholder, _}} ->
|
||||||
|
{_var, y, _n, _d} = node_details
|
||||||
|
|
||||||
|
case algo_type do
|
||||||
|
:simplify -> do_simplify(y, sorted_assumptions, context)
|
||||||
|
:check_emptiness -> do_check_emptiness(y, sorted_assumptions, context)
|
||||||
|
end
|
||||||
|
|
||||||
|
# If the constraint_id is a valid, fully-formed node, proceed with the original logic.
|
||||||
|
_ ->
|
||||||
|
# --- END IMPROVEMENT ---
|
||||||
{var, y, n, d} = node_details
|
{var, y, n, d} = node_details
|
||||||
assumptions = Map.new(sorted_assumptions)
|
assumptions = Map.new(sorted_assumptions)
|
||||||
|
|
||||||
@ -1801,18 +1848,14 @@ defmodule Tdd.Algo do
|
|||||||
constraint_for_this_assumption = Engine.unwrap_var(var)
|
constraint_for_this_assumption = Engine.unwrap_var(var)
|
||||||
|
|
||||||
id_to_intersect =
|
id_to_intersect =
|
||||||
if val, do: constraint_for_this_assumption, else: negate(constraint_for_this_assumption)
|
if val,
|
||||||
|
do: constraint_for_this_assumption,
|
||||||
|
else: negate(constraint_for_this_assumption)
|
||||||
|
|
||||||
apply(:intersect, op_intersect, acc_id, id_to_intersect)
|
apply(:intersect, op_intersect, acc_id, id_to_intersect)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
# 2. Check for the three logical outcomes:
|
# 2. Check for the three logical outcomes...
|
||||||
# - Does the path imply the constraint is satisfied?
|
|
||||||
# - Does the path imply the constraint is violated?
|
|
||||||
# - Or are both outcomes still possible?
|
|
||||||
|
|
||||||
# Implies satisfied: `sub_problem_tdd_id <: constraint_id`
|
|
||||||
# This is equivalent to `(sub_problem_tdd_id & !constraint_id)` being empty.
|
|
||||||
neg_constraint_id = negate(constraint_id)
|
neg_constraint_id = negate(constraint_id)
|
||||||
|
|
||||||
intersect_sub_with_neg_constraint =
|
intersect_sub_with_neg_constraint =
|
||||||
@ -1821,8 +1864,6 @@ defmodule Tdd.Algo do
|
|||||||
implies_satisfied =
|
implies_satisfied =
|
||||||
check_emptiness(intersect_sub_with_neg_constraint) == Store.false_node_id()
|
check_emptiness(intersect_sub_with_neg_constraint) == Store.false_node_id()
|
||||||
|
|
||||||
# Implies violated: `sub_problem_tdd_id` and `constraint_id` are disjoint.
|
|
||||||
# This is equivalent to `(sub_problem_tdd_id & constraint_id)` being empty.
|
|
||||||
intersect_sub_with_constraint =
|
intersect_sub_with_constraint =
|
||||||
apply(:intersect, op_intersect, sub_problem_tdd_id, constraint_id)
|
apply(:intersect, op_intersect, sub_problem_tdd_id, constraint_id)
|
||||||
|
|
||||||
@ -1831,21 +1872,15 @@ defmodule Tdd.Algo do
|
|||||||
# 3. Branch based on the logical outcome.
|
# 3. Branch based on the logical outcome.
|
||||||
cond do
|
cond do
|
||||||
implies_satisfied and implies_violated ->
|
implies_satisfied and implies_violated ->
|
||||||
# The sub-problem itself must be empty/impossible under the current assumptions.
|
|
||||||
# This whole path is a contradiction.
|
|
||||||
Store.false_node_id()
|
Store.false_node_id()
|
||||||
|
|
||||||
implies_satisfied ->
|
implies_satisfied ->
|
||||||
# The constraint is guaranteed by the path. Follow the 'yes' branch.
|
|
||||||
# The assumptions already imply this, so no change to them is needed.
|
|
||||||
case algo_type do
|
case algo_type do
|
||||||
:simplify -> do_simplify(y, sorted_assumptions, context)
|
:simplify -> do_simplify(y, sorted_assumptions, context)
|
||||||
:check_emptiness -> do_check_emptiness(y, sorted_assumptions, context)
|
:check_emptiness -> do_check_emptiness(y, sorted_assumptions, context)
|
||||||
end
|
end
|
||||||
|
|
||||||
implies_violated ->
|
implies_violated ->
|
||||||
# The constraint is impossible given the path. Follow the 'no' branch.
|
|
||||||
# We can add this new fact `{var, false}` to strengthen the assumptions.
|
|
||||||
new_assumptions = Map.put(assumptions, var, false) |> Enum.sort()
|
new_assumptions = Map.put(assumptions, var, false) |> Enum.sort()
|
||||||
|
|
||||||
case algo_type do
|
case algo_type do
|
||||||
@ -1854,17 +1889,13 @@ defmodule Tdd.Algo do
|
|||||||
end
|
end
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
# Neither outcome is guaranteed. Both are possible.
|
|
||||||
# We must explore both branches and combine the results.
|
|
||||||
new_assumptions_for_no_branch = Map.put(assumptions, var, false) |> Enum.sort()
|
new_assumptions_for_no_branch = Map.put(assumptions, var, false) |> Enum.sort()
|
||||||
|
|
||||||
case algo_type do
|
case algo_type do
|
||||||
:check_emptiness ->
|
:check_emptiness ->
|
||||||
# Is there ANY path to true? Explore both and take their union.
|
|
||||||
res_y = do_check_emptiness(y, sorted_assumptions, context)
|
res_y = do_check_emptiness(y, sorted_assumptions, context)
|
||||||
res_n = do_check_emptiness(n, new_assumptions_for_no_branch, context)
|
res_n = do_check_emptiness(n, new_assumptions_for_no_branch, context)
|
||||||
|
|
||||||
# Define local terminal logic for union
|
|
||||||
op_union = fn
|
op_union = fn
|
||||||
:true_terminal, _ -> :true_terminal
|
:true_terminal, _ -> :true_terminal
|
||||||
_, :true_terminal -> :true_terminal
|
_, :true_terminal -> :true_terminal
|
||||||
@ -1875,16 +1906,15 @@ defmodule Tdd.Algo do
|
|||||||
apply(:sum, op_union, res_y, res_n)
|
apply(:sum, op_union, res_y, res_n)
|
||||||
|
|
||||||
:simplify ->
|
:simplify ->
|
||||||
# Simplify both sub-trees and rebuild the node, as we cannot simplify this node away.
|
|
||||||
res_y = do_simplify(y, sorted_assumptions, context)
|
res_y = do_simplify(y, sorted_assumptions, context)
|
||||||
res_n = do_simplify(n, new_assumptions_for_no_branch, context)
|
res_n = do_simplify(n, new_assumptions_for_no_branch, context)
|
||||||
# 'dc' branch is independent of 'var'
|
|
||||||
res_d = do_simplify(d, sorted_assumptions, context)
|
res_d = do_simplify(d, sorted_assumptions, context)
|
||||||
Store.find_or_create_node(var, res_y, res_n, res_d)
|
Store.find_or_create_node(var, res_y, res_n, res_d)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defmodule Tdd.Compiler do
|
defmodule Tdd.Compiler do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user