elipl/test.exs
Kacper Marzecki 2ea586c4ad checkpoint
2025-06-15 23:47:22 +02:00

1614 lines
74 KiB
Elixir

defmodule Tdd do
@moduledoc """
# Ternary Decision Diagram (TDD) for Set-Theoretic Types in Elixir
## 1. Introduction
This document outlines the design and implementation of a Ternary Decision Diagram (TDD) based system for representing and manipulating set-theoretic types, inspired by systems like CDuce. The goal is to create a robust way to perform type checking, type inference, and other type-level computations for a rich set of datatypes, similar to those found in Elixir.
A TDD is a directed acyclic graph (DAG) used to represent a function `f(v1, v2, ..., vn) -> {true, false, dont_care}`. In our context, it represents a characteristic function for a type: given a value, the TDD determines if the value belongs to the type (`true`), does not belong (`false`), or if the specific predicates tested so far are insufficient or irrelevant for this particular type operation (`dont_care`).
The TDDs are kept **ordered** and **reduced** to ensure a canonical representation for each type, making type equivalence checks (and other operations) efficient.
- **Ordered**: Variables (predicates) appear in the same fixed global order on all paths from the root to a terminal.
- **Reduced**: Isomorphic subgraphs are merged (shared), and nodes whose children would make the test redundant under certain TDD algebra rules are eliminated or simplified.
## 2. Core TDD Structure and Operations
### 2.1. Nodes
There are two kinds of nodes:
1. **Terminal Nodes**:
* `TRUE_TERMINAL` (ID: `1`): Represents the universal set (type `any`). A path ending here means the value (or part of it) satisfies the type constraints along that path.
* `FALSE_TERMINAL` (ID: `0`): Represents the empty set (type `none`). A path ending here means the value fails the type constraints.
2. **Variable Nodes**:
* Represented as a tuple: `{variable_identifier, yes_child_id, no_child_id, dc_child_id}`.
* `variable_identifier`: A unique, globally ordered term identifying the predicate being tested at this node (e.g., "is the value an atom?", "is the integer value < 10?").
* `yes_child_id`: The ID of the next TDD node if the predicate is true.
* `no_child_id`: The ID of the next TDD node if the predicate is false.
* `dc_child_id` (Don't Care): The ID of the next TDD node if the predicate is irrelevant for the current type or operation. The semantic interpretation of `dc` is crucial and aligns with common TDD usage (e.g., for a union operation, `dc(A | B) = dc(A) | dc(B)`).
### 2.2. Node Management (`Tdd` module state)
The `Tdd` module maintains global state (currently via `Process.put/get` for simplicity, ideally a `GenServer`):
* `@nodes`: A map from `node_tuple ({variable, yes_id, no_id, dc_id})` to `node_id`. This ensures that structurally identical nodes are shared (part of the "reduced" property).
* `@node_by_id`: A map from `node_id` to its `node_tuple` or a terminal symbol (`:true_terminal`, `:false_terminal`).
* `@next_id`: The next available integer ID for a new node.
* `@op_cache`: A map for memoizing results of operations like `apply` (binary ops), `negate`, and `simplify_with_constraints`. Keys are typically `{{op_name, id1, id2}, result_id}` or `{{op_name, id1}, result_id}`.
### 2.3. Variable Ordering
A strict global total order of all possible `variable_identifier`s is essential. This is achieved by defining variable identifiers as Elixir tuples, which have a natural sort order.
The proposed structure for variable identifiers is:
`{category_integer, predicate_type_atom, specific_value_or_nested_id}`
Example categories:
* `0`: Primary type discriminators (e.g., `is_atom`, `is_integer`, `is_list`).
* `1`: Atom-specific predicates (e.g., `value == :foo`).
* `2`: Integer-specific predicates (e.g., `value < 10`).
* `4`: Tuple-specific predicates (e.g., `size == 2`, `element 0 has_type X`).
* And so on for other types.
### 2.4. Core Operations
1. **`make_node_raw(variable, yes_id, no_id, dc_id)`**:
* The fundamental private function for creating or retrieving unique structural nodes.
* Implements structural sharing via the `@nodes` table.
* Implements a basic reduction rule: if `yes_id == no_id == dc_id`, the node is redundant, and that common child ID is returned.
2. **`check_assumptions_consistency(assumptions_map)`**:
* A private helper function crucial for semantic reduction.
* Takes a map `%{variable_id => value (true/false/:dc)}` representing current path assumptions.
* Returns `:consistent` or `:contradiction` based on predefined semantic rules of the type system (e.g., `is_atom=true` AND `is_tuple=true` is a contradiction).
* This function will be expanded as more types and predicates are added.
3. **`simplify_with_constraints(tdd_id, assumptions_map)`**:
* A private, memoized, recursive function that takes a `tdd_id` and an `assumptions_map`.
* It produces a new `tdd_id` that is semantically equivalent to the input `tdd_id` under the given assumptions, but potentially simpler.
* **Crucial Behavior**: If `check_assumptions_consistency(assumptions_map)` returns `:contradiction` at any point, `simplify_with_constraints` immediately returns `@false_node_id`.
* If the TDD's variable is already in `assumptions_map`, it follows the constrained path.
* Otherwise, it recursively simplifies children, adding the current node's variable assignment to the assumptions for those deeper calls, and rebuilds the node using `make_node_raw`.
4. **`apply_raw(op_name, op_lambda, u1_id, u2_id)`**:
* The private, memoized, recursive Shannon expansion algorithm for binary set operations (union, intersection).
* `op_lambda` defines the operation on terminal nodes.
* It selects the `top_var` based on the global variable order.
* Recursively calls `apply_raw` on the children.
* Uses `make_node_raw` to construct result nodes.
* This function computes the *structural* result of the operation.
5. **Public API Operations (`sum/2`, `intersect/2`, `negate/1`)**:
* These functions orchestrate the operation:
1. Call the respective `_raw` version (e.g., `apply_raw` for `sum`/`intersect`, `negate_raw` for `negate`).
2. Take the `raw_result_id` from step 1.
3. Return `simplify_with_constraints(raw_result_id, %{})`. This final step ensures that all TDDs exposed through the public API are not only structurally canonical (via `make_node_raw` and `apply_raw`) but also *semantically canonical* (i.e., known impossible paths or contradictions are resolved to `@false_node_id`).
6. **Type Constructors (e.g., `type_atom()`, `type_atom_literal(:foo)`)**:
* These public functions build the TDD for a specific type.
* They use `make_node_raw` to define the basic structure.
* They then return `simplify_with_constraints(raw_id, %{})` to ensure the constructed type is in its simplest semantic form.
7. **`is_subtype(sub_id, super_id)`**:
* Defined as `simplify_with_constraints(intersect(sub_id, negate(super_id)), %{}) == @false_node_id`.
* Since `intersect` and `negate` now return semantically simplified TDDs, if `A ∩ ¬B` represents an empty set, the result of the intersection will be `@false_node_id`.
## 3. Datatype Representation Details
This section outlines how various Elixir-like datatypes are (or will be) represented using TDD variables and constructors. All constructors ensure the final TDD is passed through `simplify_with_constraints(raw_id, %{})`.
### 3.1. Atoms
* **Variables**:
* `@v_is_atom = {0, :is_atom}`: Primary type check.
* `v_atom_eq_A = {1, :value, A}`: Checks if the atom's value is `A`. Order by `A`.
* **Constructors**:
* `type_atom()`: Represents any atom. TDD: `make_node_raw(@v_is_atom, @true_node_id, @false_node_id, @false_node_id)`.
* `type_atom_literal(val)`: Represents a specific atom. TDD: `make_node_raw(@v_is_atom, node_for_val_eq, @false_node_id, @false_node_id)` where `node_for_val_eq = make_node_raw(v_atom_eq_A, @true_node_id, @false_node_id, @false_node_id)`.
* **Semantic Constraints for `check_assumptions_consistency`**:
* If `assumptions_map` contains `{{0, :is_atom}, true}` and `{{0, other_primary_type}, true}` -> contradiction.
* If `assumptions_map` contains `{{1, :value, A}, true}` and `{{1, :value, B}, true}` where `A != B` -> contradiction.
### 3.2. Tuples
* **Variables**:
* `@v_is_tuple = {0, :is_tuple}`: Primary type check.
* `v_tuple_size_eq_N = {4, :size, N}`: Checks if tuple size is `N`. Order by `N`.
* `v_tuple_elem_I_PRED = {4, :element, Index_I, NESTED_PREDICATE_ID}`: Predicate for element at `Index_I`. `NESTED_PREDICATE_ID` is a variable from the global order, applied to the element. (e.g., `{4, :element, 0, {0, :is_atom}}` checks if element 0 is an atom). Order by `Index_I`, then by `NESTED_PREDICATE_ID`.
* **Constructors**:
* `type_tuple()`: Any tuple.
* `type_empty_tuple()`: The tuple `{}`.
* `type_tuple_sized_any(size)`: Any tuple of a given size.
* `type_tuple_specific(element_type_ids_list)`: e.g., for `{atom(), integer()}`. This will involve creating nodes for size, then for each element, applying the TDD for that element's type.
* **Semantic Constraints**:
* `is_tuple=true` vs. other primary types.
* If `{{4, :size, N}, true}` and `{{4, :size, M}, true}` where `N != M` -> contradiction.
* If `{{4, :size, N}, true}` and a predicate `{{4, :element, I, _}, _}` exists where `I >= N` -> potential contradiction or path simplification (element doesn't exist).
### 3.3. Integers (Next to Implement)
* **Variables**:
* `@v_is_integer = {0, :is_integer}` (or a new category, e.g., `2` for integer properties).
* `v_int_eq_N = {INT_CAT, :eq, N}`.
* `v_int_lt_N = {INT_CAT, :lt, N}` (value < N).
* `v_int_gt_N = {INT_CAT, :gt, N}` (value > N).
* *(Consider also: `lte` (less than or equal), `gte` (greater than or equal) to simplify some range logic, or derive them).*
* **Constructors**:
* `type_integer()`: Any integer.
* `type_int_eq(n)`: A specific integer value.
* `type_int_lt(n)`, `type_int_gt(n)`.
* `type_int_range(min, max, min_inclusive, max_inclusive)`: Integers within a specific range.
* **Semantic Constraints**:
* `is_integer=true` vs. other primary types.
* `eq(N)` and `eq(M)` with `N != M` -> contradiction.
* `eq(N)` and `lt(M)` if `N >= M` -> contradiction.
* `eq(N)` and `gt(M)` if `N <= M` -> contradiction.
* `lt(N)` and `gt(M)` if `N <= M+1` (or `N <= M` if `gt` means `>=`) -> contradiction. (e.g., `x < 5` and `x > 4` has no integer solution).
* *Strategy for complex integer constraints*: Maintain a "current allowed interval" `[min_assumed, max_assumed]` based on `assumptions_map`. If this interval becomes empty or invalid, it's a contradiction. Each new integer assumption (`lt, gt, eq`) refines this interval.
### 3.4. Lists (Planned)
* **Variables**:
* `@v_is_list = {0, :is_list}`.
* `v_list_is_empty = {LIST_CAT, :is_empty}`.
* *If not empty*:
* `v_list_head_VAR = {LIST_CAT, :head, NESTED_PREDICATE_ID}`: Applies a global predicate to the head.
* `v_list_tail_VAR = {LIST_CAT, :tail, NESTED_PREDICATE_ID_FOR_TAIL_LIST}`: Applies a global predicate (usually list predicates) to the tail.
* *(Alternative for fixed-length lists/known structure: `{LIST_CAT, :elem, Index_I, NESTED_PREDICATE_ID}` similar to tuples).*
* **Constructors**:
* `type_list_any()`.
* `type_empty_list()`.
* `type_cons(head_type_id, tail_type_id)`.
* `type_list_of(element_type_id)`: e.g., `list(integer())`.
* **Semantic Constraints**:
* `is_list=true` vs. other primary types.
* If `is_empty=true`, then any predicate about `head` or non-empty `tail` structure is contradictory if it implies existence.
### 3.5. Strings & Binaries (Planned)
* **Variables**:
* `@v_is_binary = {0, :is_binary}`.
* `@v_is_string = {0, :is_string}` (can be a check after `is_binary` or a distinct primary type if model demands).
* Size/length predicates: `v_binary_size_eq_N`, `v_string_length_eq_N`.
* Content predicates: `v_string_eq_S`, `v_string_prefix_P`, `v_string_suffix_S`, `v_string_matches_regex_R`.
* **Semantic Constraints**: Size vs content (e.g., `size=1` and `prefix="foo"` is a contradiction). `eq(S1)` and `eq(S2)` if `S1 != S2`.
### 3.6. Maps (Planned - Complex)
* **Variables**:
* `@v_is_map = {0, :is_map}`.
* `v_map_size_eq_N`.
* `v_map_has_key_K`: (K is a canonical representation of an Elixir term).
* *If `has_key_K` is true*:
* `v_map_key_K_value_VAR = {MAP_CAT, :key_value, K, NESTED_PREDICATE_ID}`: Applies a global predicate to the value associated with key K.
* For `%{pattern_key => pattern_value}` types:
* This requires careful thought. Might involve predicates like `v_map_all_keys_matching_TYPE_X_have_values_matching_TYPE_Y`.
* **Semantic Constraints**: `is_map` vs. others. Size vs. `has_key` interactions. Contradictory type requirements for the same key's value.
### 3.7. Functions (Planned - Very Complex)
* Representation of function types (`fun((Arg1Type, Arg2Type, ...) -> ReturnType)`) is a significant challenge for TDDs.
* **Variables (Tentative)**:
* `@v_is_function = {0, :is_function}`.
* `v_fun_arity_eq_A`.
* Predicates for argument types at specific positions (e.g., `v_fun_arg_I_type_VAR`).
* Predicates for return type (e.g., `v_fun_return_type_VAR`).
* Intersection and union of function types involve concepts like contravariance of arguments and covariance of return types. This may require specialized logic beyond simple TDD operations or a very elaborate variable scheme. Often, function types are handled with auxiliary structures in type systems.
## 4. Current Status & Next Steps
* **Implemented**: Atoms, basic Tuples (any, empty, sized_any). Core TDD operations (`sum`, `intersect`, `negate`, `is_subtype`) with semantic simplification framework (`simplify_with_constraints` and `check_assumptions_consistency`).
* **Passing Tests**: A suite of tests for atom/tuple interactions, unions, intersections, negations, and subtyping, including resolution of contradictions like `atom & tuple == none`.
* **Next Immediate Step**: Implement **Integer types** as outlined in section 3.3. This will involve:
1. Defining integer-specific predicates and their global order.
2. Creating integer type constructors.
3. Significantly expanding `check_assumptions_consistency` to handle integer comparisons (`eq`, `lt`, `gt`) and their interactions.
4. Adding comprehensive tests for integers.
## 5. Future Considerations
* **Performance**: For very large TDDs or complex types, the number of nodes and cache sizes can grow. Investigate optimizations if needed.
* **Generality of `check_assumptions_consistency`**: Designing this to be easily extensible and correct for many interacting predicates is challenging. A rule-based system or a more abstract way to define predicate interactions might be beneficial.
* **"Don't Care" (`dc`) branch semantics**: Ensure the `dc` branch is consistently and correctly handled in all operations, especially `simplify_with_constraints` if assumptions can make a variable "don't care". Currently, `simplify_with_constraints` assumes `true/false/:dc` values in the `assumptions_map` if a variable is already constrained.
* **Type Inference**: Using the TDD operations to infer types or solve type constraints.
* **Polymorphism**: Representing and operating on types with free type variables. Typically, free variables are treated as `any` or involve substitution before TDD construction.
This document provides a snapshot of the current TDD system and a roadmap for its extension. The core principle is the combination of structurally canonical ROBDDs (via `make_node_raw` and `apply_raw`) with a semantic simplification layer (`simplify_with_constraints`) that embeds knowledge of the type system's rules.
"""
# --- Terminal Node IDs ---
@false_node_id 0
@true_node_id 1
defguard is_terminal_id(id) when id == @false_node_id or id == @true_node_id
def init_tdd_system do
Process.put(:nodes, %{})
Process.put(:node_by_id, %{@false_node_id => :false_terminal, @true_node_id => :true_terminal})
Process.put(:next_id, 2)
Process.put(:op_cache, %{})
IO.puts("TDD system initialized.")
end
defp get_state do
%{
nodes: Process.get(:nodes, %{}),
node_by_id:
Process.get(:node_by_id, %{
@false_node_id => :false_terminal,
@true_node_id => :true_terminal
}),
next_id: Process.get(:next_id, 2),
op_cache: Process.get(:op_cache, %{})
}
end
defp update_state(changes) do
current_state = get_state()
new_state = Map.merge(current_state, changes)
Process.put(:nodes, new_state.nodes)
Process.put(:node_by_id, new_state.node_by_id)
Process.put(:next_id, new_state.next_id)
Process.put(:op_cache, new_state.op_cache)
end
# --- 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
yes_id
else
state = get_state()
node_tuple = {variable, yes_id, no_id, dc_id}
if Map.has_key?(state.nodes, node_tuple) do
# Node already exists (structural sharing)
state.nodes[node_tuple]
else
# Create new node
new_id = state.next_id
update_state(%{
nodes: Map.put(state.nodes, node_tuple, new_id),
node_by_id: Map.put(state.node_by_id, new_id, node_tuple),
next_id: new_id + 1
})
new_id
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 ---
# assumptions_map is {variable_id => value (true, false, :dc)}
defp check_assumptions_consistency(assumptions_map) do
# Check 1: Primary type mutual exclusivity
primary_true_predicates =
Enum.reduce(assumptions_map, MapSet.new(), fn
# A variable like {0, :is_atom} is a primary type predicate
{{0, predicate_name}, true}, acc_set -> MapSet.put(acc_set, predicate_name)
_otherwise, acc_set -> acc_set
end)
if MapSet.size(primary_true_predicates) > 1 do
# IO.inspect(assumptions_map, label: "Contradiction: Primary Types")
:contradiction
else
# --- Atom-specific checks ---
# Check if any atom-specific predicate (category 1) is asserted as true.
has_true_atom_specific_pred =
Enum.any?(assumptions_map, fn {var_id, truth_value} ->
elem(var_id, 0) == 1 && truth_value == true
end)
# Check if the type is explicitly NOT an atom, or if a different primary type is asserted.
is_explicitly_not_atom_or_different_primary =
Map.get(assumptions_map, @v_is_atom) == false ||
(MapSet.size(primary_true_predicates) == 1 &&
!MapSet.member?(primary_true_predicates, :is_atom))
cond do
has_true_atom_specific_pred && is_explicitly_not_atom_or_different_primary ->
# Atom-specific predicate asserted on a confirmed non-atom type.
:contradiction
# Proceed with internal atom value checks if relevant.
true ->
atom_value_trues =
Enum.reduce(assumptions_map, MapSet.new(), fn
# An atom value predicate is like {1, :value, :some_atom}
{{1, :value, atom_val}, true}, acc_set -> MapSet.put(acc_set, atom_val)
_otherwise, acc_set -> acc_set
end)
if MapSet.size(atom_value_trues) > 1 do
# IO.inspect(assumptions_map, label: "Contradiction: Atom Values")
:contradiction
else
# --- Tuple-specific checks ---
has_true_tuple_specific_pred =
Enum.any?(assumptions_map, fn {var_id, truth_value} ->
# Category 4 for tuples
elem(var_id, 0) == 4 && truth_value == true
end)
is_explicitly_not_tuple_or_different_primary =
Map.get(assumptions_map, @v_is_tuple) == false ||
(MapSet.size(primary_true_predicates) == 1 &&
!MapSet.member?(primary_true_predicates, :is_tuple))
cond do
has_true_tuple_specific_pred && is_explicitly_not_tuple_or_different_primary ->
# Tuple-specific predicate on a confirmed non-tuple type.
:contradiction
true ->
tuple_size_trues =
Enum.reduce(assumptions_map, MapSet.new(), fn
{{4, :size, size_val}, true}, acc_set -> MapSet.put(acc_set, size_val)
_otherwise, acc_set -> acc_set
end)
if MapSet.size(tuple_size_trues) > 1 do
:contradiction
else
# --- Integer predicate checks (REVISED LOGIC from previous iteration) ---
has_true_integer_specific_pred =
Enum.any?(assumptions_map, fn {var_id, truth_value} ->
# Category 2 for integers
elem(var_id, 0) == 2 && truth_value == true
end)
is_explicitly_not_integer_or_different_primary =
Map.get(assumptions_map, @v_is_integer) == false ||
(MapSet.size(primary_true_predicates) == 1 &&
!MapSet.member?(primary_true_predicates, :is_integer))
# This flag indicates if we should even bother with interval logic.
# We check if there are integer-specific predicates *or* if the primary type is known to be integer.
should_check_integer_logic =
Enum.any?(assumptions_map, fn {var_id, _} -> elem(var_id, 0) == 2 end) ||
MapSet.member?(primary_true_predicates, :is_integer)
cond do
has_true_integer_specific_pred &&
is_explicitly_not_integer_or_different_primary ->
# Integer-specific predicate on a confirmed non-integer type.
:contradiction
should_check_integer_logic ->
initial_bounds = %{eq_val: nil, min_b: nil, max_b: nil}
bounds_acc =
Enum.reduce(
assumptions_map,
initial_bounds,
fn
# Integer predicates are category 2
{{2, :eq, n}, true}, acc ->
cond do
acc.eq_val == :conflict -> acc
is_nil(acc.eq_val) -> %{acc | eq_val: n}
# Different eq values
acc.eq_val != n -> %{acc | eq_val: :conflict}
# Same eq value, no change
true -> acc
end
# value < n => value <= n-1
{{2, :lt, n}, true}, acc ->
new_max_b_val = n - 1
updated_max_b =
if is_nil(acc.max_b),
do: new_max_b_val,
else: min(acc.max_b, new_max_b_val)
%{acc | max_b: updated_max_b}
# value > n => value >= n+1
{{2, :gt, n}, true}, acc ->
new_min_b_val = n + 1
updated_min_b =
if is_nil(acc.min_b),
do: new_min_b_val,
else: max(acc.min_b, new_min_b_val)
%{acc | min_b: updated_min_b}
# value >= n
{{2, :lt, n}, false}, acc ->
new_min_b_val = n
updated_min_b =
if is_nil(acc.min_b),
do: new_min_b_val,
else: max(acc.min_b, new_min_b_val)
%{acc | min_b: updated_min_b}
# value <= n
{{2, :gt, n}, false}, acc ->
new_max_b_val = n
updated_max_b =
if is_nil(acc.max_b),
do: new_max_b_val,
else: min(acc.max_b, new_max_b_val)
%{acc | max_b: updated_max_b}
# If is_integer is explicitly false, and we have int predicates, it's a conflict
# This is partly covered by the preamble, but good to have in reduction too.
# However, this specific check here might be complex to integrate cleanly
# with the bounds logic. The preamble should suffice.
# Non-integer predicates or non-true integer predicates
_otherwise, acc ->
acc
end
)
# Stage 1: Initial bounds from <, >, <=, >=
derived_min_b = bounds_acc.min_b
derived_max_b = bounds_acc.max_b
# Stage 2: Check for immediate conflict from <, >, <=, >=
if is_integer(derived_min_b) && is_integer(derived_max_b) &&
derived_min_b > derived_max_b do
# IO.inspect({assumptions_map, bounds_acc, derived_min_b, derived_max_b}, label: "CAC Int: Initial min > max")
:contradiction
else
# Stage 3: Incorporate equality constraint
cond do
# e.g. eq(5) and eq(7)
bounds_acc.eq_val == :conflict ->
# IO.inspect({assumptions_map, bounds_acc}, label: "CAC Int: eq_val conflict")
:contradiction
is_integer(bounds_acc.eq_val) ->
eq_v = bounds_acc.eq_val
# The value must be eq_v. Check if eq_v fits in the derived_min_b/derived_max_b interval.
min_ok = is_nil(derived_min_b) || eq_v >= derived_min_b
max_ok = is_nil(derived_max_b) || eq_v <= derived_max_b
if min_ok && max_ok do
# IO.inspect({assumptions_map, bounds_acc, eq_v, derived_min_b, derived_max_b}, label: "CAC Int: eq consistent with interval")
:consistent
else
# IO.inspect({assumptions_map, bounds_acc, eq_v, derived_min_b, derived_max_b}, label: "CAC Int: eq CONTRADICTS interval")
:contradiction
end
# No equality constraint, or no conflict from it. The initial interval check (derived_min_b > derived_max_b) suffices.
true ->
# IO.inspect({assumptions_map, bounds_acc, derived_min_b, derived_max_b}, label: "CAC Int: consistent (no eq or eq compatible)")
:consistent
end
end
# No integer contradictions to check
true ->
:consistent
end
# End integer checks
end
# End tuple size checks
end
# End tuple preamble
end
# End atom value checks
end
# End atom preamble
end
|> IO.inspect(
label: "check_assumptions_consistency END assumptions_map = #{inspect(assumptions_map)}"
)
end
# Helper for min, treating nil as infinity
defp min_opt(nil, x), do: x
defp min_opt(x, nil), do: x
defp min_opt(x, y), do: min(x, y)
# Helper for max, treating nil as -infinity
defp max_opt(nil, x), do: x
defp max_opt(x, nil), do: x
defp max_opt(x, y), do: max(x, y)
# --- Semantic Simplification (Memoized) ---
defp simplify_with_constraints(tdd_id, assumptions_map) do
state = get_state()
# Sort assumptions for cache key consistency
# Using Map.to_list and then sorting is more robust than Enum.sort_by if keys can be complex
sorted_assumptions_list = Enum.sort(Map.to_list(assumptions_map))
cache_key = {:simplify_constr, tdd_id, sorted_assumptions_list}
# 1. Check if the current assumptions_map itself is contradictory
# This initial check is crucial.
current_consistency = check_assumptions_consistency(assumptions_map)
if current_consistency == :contradiction do
# update_state(%{op_cache: Map.put(state.op_cache, cache_key, @false_node_id)}) # Cache if desired
@false_node_id
else
# 2. Handle terminal nodes
if is_terminal_id(tdd_id) do
# Terminals are final, assumptions (if consistent) don't change them
tdd_id
else
# 3. Cache lookup for non-terminal nodes
if Map.has_key?(state.op_cache, cache_key) do
state.op_cache[cache_key]
else
{var, y, n, d} = get_node_details(tdd_id)
# 4. Determine how to proceed based on 'var' and 'assumptions_map'
result_id =
case Map.get(assumptions_map, var) do
# 'var' is explicitly assumed true
true ->
simplify_with_constraints(y, assumptions_map)
# 'var' is explicitly assumed false
false ->
simplify_with_constraints(n, assumptions_map)
# 'var' is explicitly assumed don't care
:dc ->
simplify_with_constraints(d, assumptions_map)
# 'var' is NOT explicitly in assumptions_map. Check for implied truth value.
nil ->
# If (assumptions_map + var=false) is a contradiction, then var MUST be true.
implies_var_true =
check_assumptions_consistency(Map.put(assumptions_map, var, false)) ==
:contradiction
# If (assumptions_map + var=true) is a contradiction, then var MUST be false.
implies_var_false =
check_assumptions_consistency(Map.put(assumptions_map, var, true)) ==
:contradiction
# Note: We don't check for implies_var_dc here, as that's more complex.
# The original recursion handles the DC case exploration.
IO.inspect(
%{
tdd_id: tdd_id,
var: var,
assumptions: assumptions_map,
implies_var_true: implies_var_true,
implies_var_false: implies_var_false
},
label: "Simplify NIL branch"
)
cond do
implies_var_true && implies_var_false ->
# This means assumptions_map itself is contradictory.
# This should ideally be caught by the check at the very top of simplify_with_constraints.
# If reached, it implies an issue or a very complex interaction. Safest is False.
# IO.inspect({assumptions_map, var}, label: "Simplify: Contradiction from implies_var_true/false")
@false_node_id
implies_var_true ->
# Var is implied true by other assumptions. Follow the 'yes' branch.
# Pass the original 'assumptions_map' because 'var's truth is derived, not added.
simplify_with_constraints(y, assumptions_map)
implies_var_false ->
# Var is implied false. Follow the 'no' branch.
simplify_with_constraints(n, assumptions_map)
true ->
# Var's value is not forced by current assumptions. Recurse normally.
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))
make_node_raw(var, simplified_y, simplified_n, simplified_d)
end
end
update_state(%{op_cache: Map.put(state.op_cache, cache_key, result_id)})
result_id
end
end
end
end
# --- Public Node Creation (Used by Type Constructors) ---
# Type constructors will create a raw TDD and then simplify it.
defp make_node_for_constructors(variable, yes_id, no_id, dc_id) do
raw_id = make_node_raw(variable, yes_id, no_id, dc_id)
# Simplify with no initial assumptions
simplify_with_constraints(raw_id, %{})
end
def get_node_details(id) when is_terminal_id(id) do
if id == @true_node_id, do: :true_terminal, else: :false_terminal
end
def get_node_details(id) do
state = get_state()
state.node_by_id[id]
end
@v_is_atom {0, :is_atom}
@v_is_tuple {0, :is_tuple}
# New primary type
@v_is_integer {0, :is_integer}
def v_atom_eq(atom_val), do: {1, :value, atom_val}
def v_tuple_size_eq(size), do: {4, :size, size}
def v_tuple_elem_pred(index, nested_pred_id), do: {4, :element, index, nested_pred_id}
# Integer Predicates (Category 2)
def v_int_eq(n) when is_integer(n), do: {2, :eq, n}
# strictly less than n
def v_int_lt(n) when is_integer(n), do: {2, :lt, n}
# strictly greater than n
def v_int_gt(n) when is_integer(n), do: {2, :gt, n}
def type_any, do: @true_node_id
def type_none, do: @false_node_id
def type_atom do
# Raw structure: if is_atom then True, else False, dc False
# This structure is already semantically simple.
make_node_for_constructors(@v_is_atom, @true_node_id, @false_node_id, @false_node_id)
end
def type_atom_literal(atom_val) do
var_eq = v_atom_eq(atom_val)
atom_val_node = make_node_raw(var_eq, @true_node_id, @false_node_id, @false_node_id)
raw_node = make_node_raw(@v_is_atom, atom_val_node, @false_node_id, @false_node_id)
simplify_with_constraints(raw_node, %{})
end
def type_tuple do
make_node_for_constructors(@v_is_tuple, @true_node_id, @false_node_id, @false_node_id)
end
def type_empty_tuple do
var_size_0 = v_tuple_size_eq(0)
tuple_size_node = make_node_raw(var_size_0, @true_node_id, @false_node_id, @false_node_id)
raw_node = make_node_raw(@v_is_tuple, tuple_size_node, @false_node_id, @false_node_id)
simplify_with_constraints(raw_node, %{})
end
def type_tuple_sized_any(size) do
var_size = v_tuple_size_eq(size)
tuple_size_node = make_node_raw(var_size, @true_node_id, @false_node_id, @false_node_id)
raw_node = make_node_raw(@v_is_tuple, tuple_size_node, @false_node_id, @false_node_id)
simplify_with_constraints(raw_node, %{})
end
def type_integer do
make_node_for_constructors(@v_is_integer, @true_node_id, @false_node_id, @false_node_id)
end
def type_int_eq(n) do
int_eq_node = make_node_raw(v_int_eq(n), @true_node_id, @false_node_id, @false_node_id)
raw_node = make_node_raw(@v_is_integer, int_eq_node, @false_node_id, @false_node_id)
simplify_with_constraints(raw_node, %{})
end
# Represents integers x where x < n
def type_int_lt(n) do
int_lt_node = make_node_raw(v_int_lt(n), @true_node_id, @false_node_id, @false_node_id)
raw_node = make_node_raw(@v_is_integer, int_lt_node, @false_node_id, @false_node_id)
simplify_with_constraints(raw_node, %{})
end
# Represents integers x where x > n
def type_int_gt(n) do
int_gt_node = make_node_raw(v_int_gt(n), @true_node_id, @false_node_id, @false_node_id)
raw_node = make_node_raw(@v_is_integer, int_gt_node, @false_node_id, @false_node_id)
simplify_with_constraints(raw_node, %{})
end
# --- The APPLY Algorithm (Core Logic, uses make_node_raw) ---
# This function computes the raw structural result. Semantic simplification is applied by the caller.
defp apply_raw(op_name, op_lambda, u1_id, u2_id) do
state = get_state()
# apply_raw cache key
cache_key = {op_name, Enum.sort([u1_id, u2_id])}
cond do
Map.has_key?(state.op_cache, cache_key) ->
state.op_cache[cache_key]
is_terminal_id(u1_id) && is_terminal_id(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
true ->
u1_details = get_node_details(u1_id)
u2_details = get_node_details(u2_id)
result_id =
cond do
u1_details == :true_terminal or u1_details == :false_terminal ->
{var2, y2, n2, d2} = u2_details
res_y = apply_raw(op_name, op_lambda, u1_id, y2)
res_n = apply_raw(op_name, op_lambda, u1_id, n2)
res_d = apply_raw(op_name, op_lambda, u1_id, d2)
make_node_raw(var2, res_y, res_n, res_d)
u2_details == :true_terminal or u2_details == :false_terminal ->
{var1, y1, n1, d1} = u1_details
res_y = apply_raw(op_name, op_lambda, y1, u2_id)
res_n = apply_raw(op_name, op_lambda, n1, u2_id)
res_d = apply_raw(op_name, op_lambda, d1, u2_id)
make_node_raw(var1, res_y, res_n, res_d)
true ->
{var1, y1, n1, d1} = u1_details
{var2, y2, n2, d2} = u2_details
# Elixir tuple comparison
top_var = Enum.min([var1, var2])
res_y =
apply_raw(
op_name,
op_lambda,
if(var1 == top_var, do: y1, else: u1_id),
if(var2 == top_var, do: y2, else: u2_id)
)
res_n =
apply_raw(
op_name,
op_lambda,
if(var1 == top_var, do: n1, else: u1_id),
if(var2 == top_var, do: n2, else: u2_id)
)
res_d =
apply_raw(
op_name,
op_lambda,
if(var1 == top_var, do: d1, else: u1_id),
if(var2 == top_var, do: d2, else: u2_id)
)
make_node_raw(top_var, res_y, res_n, res_d)
end
update_state(%{op_cache: Map.put(state.op_cache, cache_key, result_id)})
result_id
end
end
# --- Public Set Operations (API) ---
def sum(tdd1_id, tdd2_id) do
op_lambda_sum = fn
:true_terminal, _ -> :true_terminal
_, :true_terminal -> :true_terminal
:false_terminal, t2_val -> t2_val
t1_val, :false_terminal -> t1_val
end
raw_result_id = apply_raw(:sum, op_lambda_sum, tdd1_id, tdd2_id)
simplify_with_constraints(raw_result_id, %{})
end
def intersect(tdd1_id, tdd2_id) do
op_lambda_intersect = fn
:false_terminal, _ -> :false_terminal
_, :false_terminal -> :false_terminal
:true_terminal, t2_val -> t2_val
t1_val, :true_terminal -> t1_val
end
raw_result_id = apply_raw(:intersect, op_lambda_intersect, tdd1_id, tdd2_id)
simplify_with_constraints(raw_result_id, %{})
end
def negate(tdd_id) do
# Negation also needs semantic simplification wrapper if it can create complex structures,
# but typically negation is structurally simple enough that raw ops are fine if children are simplified.
# However, to be safe and ensure canonical form for ¬(A & B) vs ¬A | ¬B.
raw_negated_id = negate_raw(tdd_id)
simplify_with_constraints(raw_negated_id, %{})
end
# Renamed original negate
defp negate_raw(tdd_id) do
state = get_state()
cache_key = {:negate_raw, tdd_id}
cond do
tdd_id == @true_node_id ->
@false_node_id
tdd_id == @false_node_id ->
@true_node_id
Map.has_key?(state.op_cache, cache_key) ->
state.op_cache[cache_key]
true ->
{var, y, n, d} = get_node_details(tdd_id)
# Negate children recursively using the public `negate` which includes simplification
# Public negate to ensure children are simplified
res_y = negate(y)
res_n = negate(n)
res_d = negate(d)
result_id = make_node_raw(var, res_y, res_n, res_d)
update_state(%{op_cache: Map.put(state.op_cache, cache_key, result_id)})
result_id
end
end
# --- Subtyping (API) ---
def is_subtype(sub_type_id, super_type_id) do
cond do
sub_type_id == super_type_id ->
true
# none is subtype of anything
sub_type_id == @false_node_id ->
true
# anything is subtype of any
super_type_id == @true_node_id ->
true
true ->
# A <: B <=> A ∩ (¬B) == ∅
# All operations (intersect, negate) now produce semantically simplified results.
IO.puts("\n--- is_subtype debug ---")
IO.inspect(sub_type_id,
label: "is_subtype: sub_type_id (#{inspect(Tdd.get_node_details(sub_type_id))})"
)
IO.inspect(super_type_id,
label: "is_subtype: super_type_id (#{inspect(Tdd.get_node_details(super_type_id))})"
)
# Tdd.print_tdd(sub_type_id)
# Tdd.print_tdd(super_type_id)
negated_super = negate(super_type_id)
IO.inspect(negated_super,
label: "is_subtype: negated_super_id (#{inspect(Tdd.get_node_details(negated_super))})"
)
# IO.puts("Structure of negated_super:")
# Tdd.print_tdd(negated_super)
intersection_result = intersect(sub_type_id, negated_super)
IO.inspect(intersection_result,
label:
"is_subtype: intersection_result_id (#{inspect(Tdd.get_node_details(intersection_result))})"
)
# IO.puts("Structure of intersection_result:")
# Tdd.print_tdd(intersection_result)
result = intersection_result == @false_node_id
IO.inspect(result, label: "is_subtype: final result")
IO.puts("--- end is_subtype debug ---\n")
result
end
end
def print_tdd(id, indent \\ 0) do
prefix = String.duplicate(" ", indent)
details = get_node_details(id)
IO.puts("#{prefix}ID #{id}: #{inspect(details)}")
case details do
{_var, y, n, d} ->
IO.puts("#{prefix} Yes ->")
print_tdd(y, indent + 1)
IO.puts("#{prefix} No ->")
print_tdd(n, indent + 1)
IO.puts("#{prefix} DC ->")
print_tdd(d, indent + 1)
:true_terminal ->
:ok
:false_terminal ->
:ok
nil ->
IO.puts("#{prefix} Error: Unknown ID #{id}")
end
end
end
# --- Example Usage ---
Tdd.init_tdd_system()
# Basic Types
tdd_foo = Tdd.type_atom_literal(:foo)
tdd_bar = Tdd.type_atom_literal(:bar)
tdd_atom = Tdd.type_atom()
tdd_empty_tuple = Tdd.type_empty_tuple()
tdd_any = Tdd.type_any()
tdd_none = Tdd.type_none()
test = fn name, expected, result ->
current_failures = Process.get(:test_failures, [])
if expected != result do
Process.put(:test_failures, [name | current_failures])
end
status = if expected == result, do: "PASSED", else: "FAILED"
IO.puts("#{name} (Expected: #{expected}) -> Result: #{result} - #{status}")
end
# Basic Types
tdd_foo = Tdd.type_atom_literal(:foo)
tdd_bar = Tdd.type_atom_literal(:bar)
tdd_baz = Tdd.type_atom_literal(:baz)
tdd_atom = Tdd.type_atom()
tdd_empty_tuple = Tdd.type_empty_tuple()
tdd_tuple = Tdd.type_tuple()
# Tuple of size 2, e.g. {any, any}
tdd_tuple_s2 = Tdd.type_tuple_sized_any(2)
tdd_any = Tdd.type_any()
tdd_none = Tdd.type_none()
test_all = fn ->
IO.puts("\n--- TDD for :foo ---")
Tdd.print_tdd(tdd_foo)
IO.puts("\n--- TDD for not :foo ---")
Tdd.print_tdd(Tdd.negate(tdd_foo))
IO.puts("\n--- TDD for atom ---")
Tdd.print_tdd(tdd_atom)
IO.puts("\n--- TDD for not atom ---")
# Expected: make_node(@v_is_atom, @false_node_id, @true_node_id, @true_node_id)
# This represents "anything that is not an atom". The DC branch becomes true because if
# "is_atom" test is irrelevant for "not atom", it means it's part of "not atom".
Tdd.print_tdd(Tdd.negate(tdd_atom))
IO.puts("\n--- TDD for :foo and :bar (should be none) ---")
tdd_foo_and_bar = Tdd.intersect(tdd_foo, tdd_bar)
# Expected ID 0: :false_terminal
Tdd.print_tdd(tdd_foo_and_bar)
IO.puts("\n--- TDD for :foo and atom (should be :foo) ---")
tdd_foo_and_atom = Tdd.intersect(tdd_foo, tdd_atom)
# Expected to be structurally identical to tdd_foo
Tdd.print_tdd(tdd_foo_and_atom)
IO.puts("\n--- Basic Subtyping Tests ---")
test.(":foo <: atom", true, Tdd.is_subtype(tdd_foo, tdd_atom))
test.("atom <: :foo", false, Tdd.is_subtype(tdd_atom, tdd_foo))
test.(":foo <: :bar", false, Tdd.is_subtype(tdd_foo, tdd_bar))
test.(":foo <: :foo", true, Tdd.is_subtype(tdd_foo, tdd_foo))
test.("{} <: tuple", true, Tdd.is_subtype(tdd_empty_tuple, tdd_tuple))
test.("tuple <: {}", false, Tdd.is_subtype(tdd_tuple, tdd_empty_tuple))
test.(":foo <: {}", false, Tdd.is_subtype(tdd_foo, tdd_empty_tuple))
test.("tuple_size_2 <: tuple", true, Tdd.is_subtype(tdd_tuple_s2, tdd_tuple))
test.("tuple <: tuple_size_2", false, Tdd.is_subtype(tdd_tuple, tdd_tuple_s2))
test.("tuple_size_2 <: {}", false, Tdd.is_subtype(tdd_tuple_s2, tdd_empty_tuple))
IO.puts("\n--- Any/None Subtyping Tests ---")
test.("any <: :foo", false, Tdd.is_subtype(tdd_any, tdd_foo))
test.(":foo <: any", true, Tdd.is_subtype(tdd_foo, tdd_any))
test.("none <: :foo", true, Tdd.is_subtype(tdd_none, tdd_foo))
test.(":foo <: none", false, Tdd.is_subtype(tdd_foo, tdd_none))
test.("none <: any", true, Tdd.is_subtype(tdd_none, tdd_any))
test.("any <: none", false, Tdd.is_subtype(tdd_any, tdd_none))
test.("any <: any", true, Tdd.is_subtype(tdd_any, tdd_any))
test.("none <: none", true, Tdd.is_subtype(tdd_none, tdd_none))
IO.puts("\n--- Union related Subtyping ---")
tdd_foo_or_bar = Tdd.sum(tdd_foo, tdd_bar)
tdd_foo_or_bar_or_baz = Tdd.sum(tdd_foo_or_bar, tdd_baz)
test.(":foo <: (:foo | :bar)", true, Tdd.is_subtype(tdd_foo, tdd_foo_or_bar))
test.(":baz <: (:foo | :bar)", false, Tdd.is_subtype(tdd_baz, tdd_foo_or_bar))
test.("(:foo | :bar) <: atom", true, Tdd.is_subtype(tdd_foo_or_bar, tdd_atom))
test.("atom <: (:foo | :bar)", false, Tdd.is_subtype(tdd_atom, tdd_foo_or_bar))
test.(
"(:foo | :bar) <: (:foo | :bar | :baz)",
true,
Tdd.is_subtype(tdd_foo_or_bar, tdd_foo_or_bar_or_baz)
)
test.(
"(:foo | :bar | :baz) <: (:foo | :bar)",
false,
Tdd.is_subtype(tdd_foo_or_bar_or_baz, tdd_foo_or_bar)
)
# Test against a non-member of the union
test.("(:foo | :bar) <: :baz", false, Tdd.is_subtype(tdd_foo_or_bar, tdd_baz))
IO.puts("\n--- Intersection related Subtyping ---")
# Should be equivalent to tdd_foo
tdd_atom_and_foo = Tdd.intersect(tdd_atom, tdd_foo)
# Should be tdd_none
tdd_atom_and_tuple = Tdd.intersect(tdd_atom, tdd_tuple)
test.("(atom & :foo) <: :foo", true, Tdd.is_subtype(tdd_atom_and_foo, tdd_foo))
test.(":foo <: (atom & :foo)", true, Tdd.is_subtype(tdd_foo, tdd_atom_and_foo))
test.("(atom & tuple) <: none", true, Tdd.is_subtype(tdd_atom_and_tuple, tdd_none))
test.("none <: (atom & tuple)", true, Tdd.is_subtype(tdd_none, tdd_atom_and_tuple))
test.("(atom & :foo) <: :bar", false, Tdd.is_subtype(tdd_atom_and_foo, tdd_bar))
# An intersection is a subtype of its components
test.("(atom & :foo) <: atom", true, Tdd.is_subtype(tdd_atom_and_foo, tdd_atom))
# (none <: atom)
test.("(atom & tuple) <: atom", true, Tdd.is_subtype(tdd_atom_and_tuple, tdd_atom))
# (none <: tuple)
test.("(atom & tuple) <: tuple", true, Tdd.is_subtype(tdd_atom_and_tuple, tdd_tuple))
IO.puts("\n--- Negation related Subtyping (Contrapositives) ---")
# Reminder: ¬A <: ¬B is equivalent to B <: A (contrapositive)
# Test 1: ¬atom <: ¬:foo (Equivalent to :foo <: atom, which is true)
test.("¬atom <: ¬:foo", true, Tdd.is_subtype(Tdd.negate(tdd_atom), Tdd.negate(tdd_foo)))
# Test 2: ¬:foo <: ¬atom (Equivalent to atom <: :foo, which is false)
test.("¬:foo <: ¬atom", false, Tdd.is_subtype(Tdd.negate(tdd_foo), Tdd.negate(tdd_atom)))
# Double negation
test.("¬(¬:foo) <: :foo", true, Tdd.is_subtype(Tdd.negate(Tdd.negate(tdd_foo)), tdd_foo))
test.(":foo <: ¬(¬:foo)", true, Tdd.is_subtype(tdd_foo, Tdd.negate(Tdd.negate(tdd_foo))))
# Disjoint types
test.("atom <: ¬tuple", true, Tdd.is_subtype(tdd_atom, Tdd.negate(tdd_tuple)))
test.("tuple <: ¬atom", true, Tdd.is_subtype(tdd_tuple, Tdd.negate(tdd_atom)))
test.(":foo <: ¬{}", true, Tdd.is_subtype(tdd_foo, Tdd.negate(tdd_empty_tuple)))
IO.puts("\n--- Mixed Types & Complex Subtyping ---")
tdd_atom_or_tuple = Tdd.sum(tdd_atom, tdd_tuple)
tdd_foo_or_empty_tuple = Tdd.sum(tdd_foo, tdd_empty_tuple)
test.(
"(:foo | {}) <: (atom | tuple)",
true,
Tdd.is_subtype(tdd_foo_or_empty_tuple, tdd_atom_or_tuple)
)
test.(
"(atom | tuple) <: (:foo | {})",
false,
Tdd.is_subtype(tdd_atom_or_tuple, tdd_foo_or_empty_tuple)
)
test.(":foo <: (atom | tuple)", true, Tdd.is_subtype(tdd_foo, tdd_atom_or_tuple))
test.("{} <: (atom | tuple)", true, Tdd.is_subtype(tdd_empty_tuple, tdd_atom_or_tuple))
# De Morgan's Law illustration (A | B = ¬(¬A & ¬B))
# (:foo | :bar) <: ¬(¬:foo & ¬:bar)
tdd_not_foo_and_not_bar = Tdd.intersect(Tdd.negate(tdd_foo), Tdd.negate(tdd_bar))
test.(
"(:foo | :bar) <: ¬(¬:foo & ¬:bar)",
true,
Tdd.is_subtype(tdd_foo_or_bar, Tdd.negate(tdd_not_foo_and_not_bar))
)
test.(
"¬(¬:foo & ¬:bar) <: (:foo | :bar)",
true,
Tdd.is_subtype(Tdd.negate(tdd_not_foo_and_not_bar), tdd_foo_or_bar)
)
# Type difference: atom - :foo (represented as atom & ¬:foo)
tdd_atom_minus_foo = Tdd.intersect(tdd_atom, Tdd.negate(tdd_foo))
test.("(atom - :foo) <: atom", true, Tdd.is_subtype(tdd_atom_minus_foo, tdd_atom))
test.("(atom - :foo) <: :foo", false, Tdd.is_subtype(tdd_atom_minus_foo, tdd_foo))
# True if :bar is in (atom - :foo)
test.("(atom - :foo) <: :bar", false, Tdd.is_subtype(tdd_atom_minus_foo, tdd_bar))
test.(":bar <: (atom - :foo)", true, Tdd.is_subtype(tdd_bar, tdd_atom_minus_foo))
# (atom - :foo) | :foo should be atom
tdd_recombined_atom = Tdd.sum(tdd_atom_minus_foo, tdd_foo)
test.("((atom - :foo) | :foo) <: atom", true, Tdd.is_subtype(tdd_recombined_atom, tdd_atom))
test.("atom <: ((atom - :foo) | :foo)", true, Tdd.is_subtype(tdd_atom, tdd_recombined_atom))
# (atom | {}) & (tuple | :foo) must be (:foo | {})
# Represents `atom() | {}`
tdd_atom_or_empty = Tdd.sum(tdd_atom, tdd_empty_tuple)
# Represents `tuple() | :foo`
tdd_tuple_or_foo = Tdd.sum(tdd_tuple, tdd_foo)
intersected_complex = Tdd.intersect(tdd_atom_or_empty, tdd_tuple_or_foo)
# Expected result for intersected_complex is tdd_foo_or_empty_tuple
test.(
"(atom | {}) & (tuple | :foo) <: (:foo | {})",
true,
Tdd.is_subtype(intersected_complex, tdd_foo_or_empty_tuple)
)
test.(
"(:foo | {}) <: (atom | {}) & (tuple | :foo)",
true,
Tdd.is_subtype(tdd_foo_or_empty_tuple, intersected_complex)
)
# {} | tuple_size_2 should be a subtype of tuple
tdd_empty_or_s2 = Tdd.sum(tdd_empty_tuple, tdd_tuple_s2)
test.("({} | tuple_size_2) <: tuple", true, Tdd.is_subtype(tdd_empty_or_s2, tdd_tuple))
test.(
"({} | tuple_size_2) <: ({} | tuple_size_2)",
true,
Tdd.is_subtype(tdd_empty_or_s2, tdd_empty_or_s2)
)
test.(
"({} | tuple_size_2) <: tuple_size_2",
false,
Tdd.is_subtype(tdd_empty_or_s2, tdd_tuple_s2)
)
IO.puts("\n--- TDD structure for (atom - :foo) ---")
Tdd.print_tdd(tdd_atom_minus_foo)
IO.puts("\n--- TDD structure for ((atom - :foo) | :foo) which should be 'atom' ---")
Tdd.print_tdd(tdd_recombined_atom)
IO.puts("\n--- TDD structure for 'atom' for comparison ---")
Tdd.print_tdd(tdd_atom)
IO.inspect(Process.get(:test_failures, []))
end
defmodule IntegerTests do
def run(test_fn) do
Process.put(:test_failures, [])
# Reset for each test group if needed, or once globally
Tdd.init_tdd_system()
# Integer types
tdd_int = Tdd.type_integer()
tdd_int_5 = Tdd.type_int_eq(5)
tdd_int_7 = Tdd.type_int_eq(7)
# x < 10
tdd_int_lt_10 = Tdd.type_int_lt(10)
# x > 3
tdd_int_gt_3 = Tdd.type_int_gt(3)
# x < 3
tdd_int_lt_3 = Tdd.type_int_lt(3)
# x > 10
tdd_int_gt_10 = Tdd.type_int_gt(10)
tdd_atom_foo = Tdd.type_atom_literal(:foo)
IO.puts("\n--- Integer Type Structures ---")
IO.puts("Integer:")
Tdd.print_tdd(tdd_int)
IO.puts("Int == 5:")
Tdd.print_tdd(tdd_int_5)
IO.puts("Int < 10:")
Tdd.print_tdd(tdd_int_lt_10)
IO.puts("\n--- Integer Subtyping Tests ---")
test_fn.("int_5 <: integer", true, Tdd.is_subtype(tdd_int_5, tdd_int))
test_fn.("integer <: int_5", false, Tdd.is_subtype(tdd_int, tdd_int_5))
test_fn.("int_5 <: int_7", false, Tdd.is_subtype(tdd_int_5, tdd_int_7))
test_fn.("int_5 <: int_5", true, Tdd.is_subtype(tdd_int_5, tdd_int_5))
test_fn.("int_5 <: atom_foo", false, Tdd.is_subtype(tdd_int_5, tdd_atom_foo))
test_fn.("int_lt_10 <: integer", true, Tdd.is_subtype(tdd_int_lt_10, tdd_int))
test_fn.("integer <: int_lt_10", false, Tdd.is_subtype(tdd_int, tdd_int_lt_10))
# 5 < 10
test_fn.("int_5 <: int_lt_10", true, Tdd.is_subtype(tdd_int_5, tdd_int_lt_10))
test_fn.("int_lt_10 <: int_5", false, Tdd.is_subtype(tdd_int_lt_10, tdd_int_5))
test_fn.("int_gt_3 <: integer", true, Tdd.is_subtype(tdd_int_gt_3, tdd_int))
# 5 > 3
test_fn.("int_5 <: int_gt_3", true, Tdd.is_subtype(tdd_int_5, tdd_int_gt_3))
test_fn.("int_gt_3 <: int_5", false, Tdd.is_subtype(tdd_int_gt_3, tdd_int_5))
# x < 3 implies x < 10
test_fn.("int_lt_3 <: int_lt_10", true, Tdd.is_subtype(tdd_int_lt_3, tdd_int_lt_10))
# x > 10 implies x > 3
test_fn.("int_gt_10 <: int_gt_3", true, Tdd.is_subtype(tdd_int_gt_10, tdd_int_gt_3))
test_fn.("int_lt_10 <: int_lt_3", false, Tdd.is_subtype(tdd_int_lt_10, tdd_int_lt_3))
test_fn.("int_gt_3 <: int_gt_10", false, Tdd.is_subtype(tdd_int_gt_3, tdd_int_gt_10))
IO.puts("\n--- Integer Intersection Tests (should resolve to none for contradictions) ---")
intersect_5_7 = Tdd.intersect(tdd_int_5, tdd_int_7)
test_fn.("int_5 & int_7 == none", true, intersect_5_7 == Tdd.type_none())
IO.puts("Structure of int_5 & int_7 (should be ID 0):")
Tdd.print_tdd(intersect_5_7)
# x < 3 AND x > 10
intersect_lt3_gt10 = Tdd.intersect(tdd_int_lt_3, tdd_int_gt_10)
test_fn.("int_lt_3 & int_gt_10 == none", true, intersect_lt3_gt10 == Tdd.type_none())
IO.puts("Structure of int_lt_3 & int_gt_10 (should be ID 0):")
Tdd.print_tdd(intersect_lt3_gt10)
# x < 10 AND x > 3 (e.g. 4,5..9)
intersect_lt10_gt3 = Tdd.intersect(tdd_int_lt_10, tdd_int_gt_3)
test_fn.("int_lt_10 & int_gt_3 != none", true, intersect_lt10_gt3 != Tdd.type_none())
IO.puts("Structure of int_lt_10 & int_gt_3 (should be non-empty):")
Tdd.print_tdd(intersect_lt10_gt3)
# Test a value within this intersection
test_fn.(
"int_5 <: (int_lt_10 & int_gt_3)",
true,
Tdd.is_subtype(tdd_int_5, intersect_lt10_gt3)
)
# x == 5 AND x < 3
intersect_5_lt3 = Tdd.intersect(tdd_int_5, tdd_int_lt_3)
test_fn.("int_5 & int_lt_3 == none", true, intersect_5_lt3 == Tdd.type_none())
IO.puts("\n--- Integer Union Tests ---")
union_5_7 = Tdd.sum(tdd_int_5, tdd_int_7)
test_fn.("int_5 <: (int_5 | int_7)", true, Tdd.is_subtype(tdd_int_5, union_5_7))
test_fn.("int_7 <: (int_5 | int_7)", true, Tdd.is_subtype(tdd_int_7, union_5_7))
test_fn.("int_lt_3 <: (int_5 | int_7)", false, Tdd.is_subtype(tdd_int_lt_3, union_5_7))
IO.puts("Structure of int_5 | int_7:")
Tdd.print_tdd(union_5_7)
# (int < 3) | (int > 10)
union_disjoint_ranges = Tdd.sum(tdd_int_lt_3, tdd_int_gt_10)
test_fn.(
"int_eq(0) <: (int < 3 | int > 10)",
true,
Tdd.is_subtype(Tdd.type_int_eq(0), union_disjoint_ranges)
)
test_fn.(
"int_eq(5) <: (int < 3 | int > 10)",
false,
Tdd.is_subtype(Tdd.type_int_eq(5), union_disjoint_ranges)
)
test_fn.(
"int_eq(12) <: (int < 3 | int > 10)",
true,
Tdd.is_subtype(Tdd.type_int_eq(12), union_disjoint_ranges)
)
IO.inspect(Process.get(:test_failures, []))
end
end
# test_all.()
# IntegerTests.run(test)
tdd_int_gt_10 = Tdd.type_int_gt(10)
tdd_int_gt_3 = Tdd.type_int_gt(3)
test.("int_gt_10 <: int_gt_3", true, Tdd.is_subtype(tdd_int_gt_10, tdd_int_gt_3))
# output:
#
# TDD system initialized.
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_atom},
# tdd_id: 3,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :foo} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :foo} => true}: :consistent
# Simplify NIL branch: %{
# var: {1, :value, :foo},
# tdd_id: 2,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_atom} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :foo} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :foo} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :foo} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_atom},
# tdd_id: 5,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :bar} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :bar} => true}: :consistent
# Simplify NIL branch: %{
# var: {1, :value, :bar},
# tdd_id: 4,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_atom} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :bar} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :bar} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :bar} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_atom},
# tdd_id: 6,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_tuple},
# tdd_id: 8,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 0} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 0} => true}: :consistent
# Simplify NIL branch: %{
# var: {4, :size, 0},
# tdd_id: 7,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_tuple} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 0} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 0} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 0} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_atom},
# tdd_id: 10,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :baz} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :baz} => true}: :consistent
# Simplify NIL branch: %{
# var: {1, :value, :baz},
# tdd_id: 9,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_atom} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :baz} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :baz} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => true, {1, :value, :baz} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_atom} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_tuple},
# tdd_id: 11,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_tuple},
# tdd_id: 13,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 2} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 2} => true}: :consistent
# Simplify NIL branch: %{
# var: {4, :size, 2},
# tdd_id: 12,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_tuple} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 2} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 2} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => true, {4, :size, 2} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_tuple} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_integer},
# tdd_id: 15,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 10} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 10} => true}: :consistent
# Simplify NIL branch: %{
# var: {2, :gt, 10},
# tdd_id: 14,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_integer} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 10} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 10} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 10} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_integer},
# tdd_id: 17,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => true}: :consistent
# Simplify NIL branch: %{
# var: {2, :gt, 3},
# tdd_id: 16,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_integer} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => :dc}: :consistent
#
# --- is_subtype debug ---
# is_subtype: sub_type_id ({{0, :is_integer}, 14, 0, 0}): 15
# is_subtype: super_type_id ({{0, :is_integer}, 16, 0, 0}): 17
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{2, :gt, 3} => true}: :consistent
# Simplify NIL branch: %{
# var: {2, :gt, 3},
# tdd_id: 18,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{2, :gt, 3} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{2, :gt, 3} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_integer},
# tdd_id: 19,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => true}: :consistent
# Simplify NIL branch: %{
# var: {2, :gt, 3},
# tdd_id: 18,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_integer} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => :dc}: :consistent
# is_subtype: negated_super_id ({{0, :is_integer}, 18, 1, 1}): 19
# check_assumptions_consistency END assumptions_map = %{}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true}: :consistent
# Simplify NIL branch: %{
# var: {0, :is_integer},
# tdd_id: 21,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => true}: :consistent
# Simplify NIL branch: %{
# var: {2, :gt, 3},
# tdd_id: 20,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_integer} => true}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false, {2, :gt, 10} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false, {2, :gt, 10} => true}: :contradiction
# Simplify NIL branch: %{
# var: {2, :gt, 10},
# tdd_id: 14,
# implies_var_true: false,
# implies_var_false: true,
# assumptions: %{{0, :is_integer} => true, {2, :gt, 3} => false}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => :dc, {2, :gt, 10} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => :dc, {2, :gt, 10} => true}: :consistent
# Simplify NIL branch: %{
# var: {2, :gt, 10},
# tdd_id: 14,
# implies_var_true: false,
# implies_var_false: false,
# assumptions: %{{0, :is_integer} => true, {2, :gt, 3} => :dc}
# }
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => :dc, {2, :gt, 10} => true}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => :dc, {2, :gt, 10} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => true, {2, :gt, 3} => :dc, {2, :gt, 10} => :dc}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => false}: :consistent
# check_assumptions_consistency END assumptions_map = %{{0, :is_integer} => :dc}: :consistent
# is_subtype: intersection_result_id ({{0, :is_integer}, 22, 0, 0}): 23
# is_subtype: final result: false
# --- end is_subtype debug ---
#
# int_gt_10 <: int_gt_3 (Expected: true) -> Result: false - FAILED