checkpoint

This commit is contained in:
Kacper Marzecki 2025-06-15 20:09:03 +02:00
parent 3e0d6f3485
commit eb2e8855ac

105
test.exs
View File

@ -45,7 +45,10 @@ defmodule Tdd do
Process.put(:op_cache, new_state.op_cache) Process.put(:op_cache, new_state.op_cache)
end end
def make_node(variable, yes_id, no_id, dc_id) do # --- Raw Node Creation (Structural) ---
# This is the original make_node, focused on structural uniqueness and basic reduction rule.
defp make_node_raw(variable, yes_id, no_id, dc_id) do
# Basic reduction: if all children are identical, this node is redundant.
if yes_id == no_id && yes_id == dc_id do if yes_id == no_id && yes_id == dc_id do
yes_id yes_id
else else
@ -53,21 +56,94 @@ defmodule Tdd do
node_tuple = {variable, yes_id, no_id, dc_id} node_tuple = {variable, yes_id, no_id, dc_id}
if Map.has_key?(state.nodes, node_tuple) do if Map.has_key?(state.nodes, node_tuple) do
# Node already exists (structural sharing)
state.nodes[node_tuple] state.nodes[node_tuple]
else else
# Create new node
new_id = state.next_id new_id = state.next_id
update_state(%{ update_state(%{
nodes: Map.put(state.nodes, node_tuple, new_id), nodes: Map.put(state.nodes, node_tuple, new_id),
node_by_id: Map.put(state.node_by_id, new_id, node_tuple), node_by_id: Map.put(state.node_by_id, new_id, node_tuple),
next_id: new_id + 1 next_id: new_id + 1
}) })
new_id new_id
end end
end end
end end
# --- Public Node Creation (Currently same as raw, apply will handle context) ---
# The `apply` algorithm inherently creates the necessary structure.
# Semantic simplification is applied *after* `apply` completes.
def make_node(variable, yes_id, no_id, dc_id) do
make_node_raw(variable, yes_id, no_id, dc_id)
end
# --- Semantic Constraint Checking ---
defp check_assumptions_consistency(assumptions_map) do
# Primary type mutual exclusivity:
# Check if more than one primary type (category 0) is assumed to be true.
primary_true_predicates =
Enum.reduce(assumptions_map, MapSet.new(), fn
({{0, predicate_name}, true}, acc_set) -> MapSet.put(acc_set, predicate_name)
# ({var, false}, acc_set) -> # If is_atom is false, that's fine with is_tuple being true.
# Check if var is a primary type and its assumed value contradicts another assumed primary type.
# E.g. assumptions_map has { {0, :is_atom}, true } and we are checking an assumption for { {0, :is_tuple}, true }
# This is implicitly handled by iterating through assumptions_map.
_otherwise, acc_set -> acc_set
end)
if MapSet.size(primary_true_predicates) > 1 do
# IO.puts "Debug: Contradiction! Assumptions: #{inspect assumptions_map}, True primary types: #{inspect primary_true_predicates}"
:contradiction
else
# Add other semantic checks here, e.g.:
# - If assumptions include `{v_int_lt_N, true}` and `{v_int_gt_M, true}` where N <= M.
# - If assumptions include `{v_tuple_size_eq_2, true}` and `{v_tuple_size_eq_3, true}`.
:consistent
end
end
# --- Semantic Simplification ---
# Simplifies a TDD given a map of assumptions {variable => value}.
# Value can be true, false, or :dc (if we want to model "don't care" assumptions).
defp simplify_with_constraints(tdd_id, assumptions_map) do
state = get_state()
# Canonicalize assumptions_map for cache key: sort map by variable
# Convert map to sorted list of tuples for stable cache key
sorted_assumptions_list = Enum.sort_by(assumptions_map, fn {var, _val} -> var end)
cache_key = {:simplify_constr, tdd_id, sorted_assumptions_list}
cond do
is_terminal_id(tdd_id) ->
tdd_id
Map.has_key?(state.op_cache, cache_key) ->
state.op_cache[cache_key]
check_assumptions_consistency(assumptions_map) == :contradiction ->
# If assumptions are contradictory, the entire path is impossible.
# update_state(%{op_cache: Map.put(state.op_cache, cache_key, @false_node_id)}) # Cache this finding
@false_node_id
true ->
{var, y, n, d} = get_node_details(tdd_id)
result_id =
case Map.get(assumptions_map, var) do
true -> # Current var is assumed true, follow yes branch with same assumptions
simplify_with_constraints(y, assumptions_map)
false -> # Current var is assumed false, follow no branch with same assumptions
simplify_with_constraints(n, assumptions_map)
:dc -> # Current var is assumed don't care, follow dc branch
simplify_with_constraints(d, assumptions_map)
nil -> # Current var is not in assumptions, so it's a decision point.
# Recursively simplify children by adding this var's assignment to assumptions.
simplified_y = simplify_with_constraints(y, Map.put(assumptions_map, var, true))
simplified_n = simplify_with_constraints(n, Map.put(assumptions_map, var, false))
simplified_d = simplify_with_constraints(d, Map.put(assumptions_map, var, :dc)) # Assuming :dc can be an assumption value
make_node_raw(var, simplified_y, simplified_n, simplified_d) # Use raw, as children are now simplified
end
update_state(%{op_cache: Map.put(state.op_cache, cache_key, result_id)})
result_id
end
end
def get_node_details(id) when is_terminal_id(id) do def get_node_details(id) when is_terminal_id(id) do
if id == @true_node_id, do: :true_terminal, else: :false_terminal if id == @true_node_id, do: :true_terminal, else: :false_terminal
end end
@ -113,6 +189,7 @@ defmodule Tdd do
make_node(@v_is_tuple, tuple_size_node, @false_node_id, @false_node_id) make_node(@v_is_tuple, tuple_size_node, @false_node_id, @false_node_id)
end end
# --- The APPLY Algorithm (uses make_node, which is currently make_node_raw) ---
def apply(op_name, op_lambda, u1_id, u2_id) do def apply(op_name, op_lambda, u1_id, u2_id) do
state = get_state() state = get_state()
cache_key = {op_name, Enum.sort([u1_id, u2_id])} cache_key = {op_name, Enum.sort([u1_id, u2_id])}
@ -125,26 +202,29 @@ defmodule Tdd do
res_terminal_symbol = op_lambda.(get_node_details(u1_id), get_node_details(u2_id)) res_terminal_symbol = op_lambda.(get_node_details(u1_id), get_node_details(u2_id))
if res_terminal_symbol == :true_terminal, do: @true_node_id, else: @false_node_id if res_terminal_symbol == :true_terminal, do: @true_node_id, else: @false_node_id
true -> true -> # Recursive case
u1_details = get_node_details(u1_id) u1_details = get_node_details(u1_id)
u2_details = get_node_details(u2_id) u2_details = get_node_details(u2_id)
result_id = result_id =
cond do cond do
# Case: u1 is terminal, u2 is not
u1_details == :true_terminal or u1_details == :false_terminal -> u1_details == :true_terminal or u1_details == :false_terminal ->
{var2, y2, n2, d2} = u2_details {var2, y2, n2, d2} = u2_details
res_y = apply(op_name, op_lambda, u1_id, y2) res_y = apply(op_name, op_lambda, u1_id, y2)
res_n = apply(op_name, op_lambda, u1_id, n2) res_n = apply(op_name, op_lambda, u1_id, n2)
res_d = apply(op_name, op_lambda, u1_id, d2) res_d = apply(op_name, op_lambda, u1_id, d2)
make_node(var2, res_y, res_n, res_d) make_node(var2, res_y, res_n, res_d) # make_node is make_node_raw
# Case: u2 is terminal, u1 is not
u2_details == :true_terminal or u2_details == :false_terminal -> u2_details == :true_terminal or u2_details == :false_terminal ->
{var1, y1, n1, d1} = u1_details {var1, y1, n1, d1} = u1_details
res_y = apply(op_name, op_lambda, y1, u2_id) res_y = apply(op_name, op_lambda, y1, u2_id)
res_n = apply(op_name, op_lambda, n1, u2_id) res_n = apply(op_name, op_lambda, n1, u2_id)
res_d = apply(op_name, op_lambda, d1, u2_id) res_d = apply(op_name, op_lambda, d1, u2_id)
make_node(var1, res_y, res_n, res_d) make_node(var1, res_y, res_n, res_d) # make_node is make_node_raw
# Case: Both u1 and u2 are non-terminal nodes
true -> true ->
{var1, y1, n1, d1} = u1_details {var1, y1, n1, d1} = u1_details
{var2, y2, n2, d2} = u2_details {var2, y2, n2, d2} = u2_details
@ -153,33 +233,30 @@ defmodule Tdd do
cond do cond do
var1 == var2 -> var1 var1 == var2 -> var1
var1 < var2 -> var1 var1 < var2 -> var1
true -> var2 true -> var2 # var2 < var1
end end
# Recursive calls based on top_var
res_y = res_y =
cond do cond do
top_var == var1 && top_var == var2 -> apply(op_name, op_lambda, y1, y2) top_var == var1 && top_var == var2 -> apply(op_name, op_lambda, y1, y2)
top_var == var1 -> apply(op_name, op_lambda, y1, u2_id) top_var == var1 -> apply(op_name, op_lambda, y1, u2_id) # u2_id passed through
true -> apply(op_name, op_lambda, u1_id, y2) true -> apply(op_name, op_lambda, u1_id, y2) # u1_id passed through
end end
res_n = res_n =
cond do cond do
top_var == var1 && top_var == var2 -> apply(op_name, op_lambda, n1, n2) top_var == var1 && top_var == var2 -> apply(op_name, op_lambda, n1, n2)
top_var == var1 -> apply(op_name, op_lambda, n1, u2_id) top_var == var1 -> apply(op_name, op_lambda, n1, u2_id)
true -> apply(op_name, op_lambda, u1_id, n2) true -> apply(op_name, op_lambda, u1_id, n2)
end end
res_d = res_d =
cond do cond do
top_var == var1 && top_var == var2 -> apply(op_name, op_lambda, d1, d2) top_var == var1 && top_var == var2 -> apply(op_name, op_lambda, d1, d2)
top_var == var1 -> apply(op_name, op_lambda, d1, u2_id) top_var == var1 -> apply(op_name, op_lambda, d1, u2_id)
true -> apply(op_name, op_lambda, u1_id, d2) true -> apply(op_name, op_lambda, u1_id, d2)
end end
make_node(top_var, res_y, res_n, res_d) # make_node is make_node_raw
make_node(top_var, res_y, res_n, res_d)
end end
update_state(%{op_cache: Map.put(state.op_cache, cache_key, result_id)}) update_state(%{op_cache: Map.put(state.op_cache, cache_key, result_id)})
result_id result_id
end end