Metadata-Version: 2.4
Name: PhantomTrace
Version: 0.5.0
Summary: PhantomTrace — a mathematical framework where numbers exist in present or absent states with custom operations to include addition, subtraction, multiplication, division, and erasure.
Author: PhantomTrace Project
License: MIT
Project-URL: Homepage, https://github.com/phantomtrace/phantomtrace
Project-URL: Documentation, https://github.com/phantomtrace/phantomtrace#readme
Project-URL: Issues, https://github.com/phantomtrace/phantomtrace/issues
Keywords: math,calculus,absence,abstract-algebra,number-theory,phantomtrace,dual-state
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file
Dynamic: requires-python

# PhantomTrace

A Python library implementing an experimental mathematical framework where numbers can exist in two states: **present** or **absent**. It defines five operations that interact with these states in consistent, rule-based ways.

Zero is redefined: `0` is not emptiness — it's one absence (`1(0)`). This means every operation has a defined result, including division by zero.

**Read the paper**: [Absence Theory](https://www.academia.edu/150254484/Absence_Theory_Quantified_Absence_and_State_Aware_Arithmetic_within_Domains_of_Reference)

## Installation

```bash
pip install phantomtrace
```

## Shorthand Notation (v0.3.0)

Use the `n()` shorthand to create numbers quickly:

```python
from absence_calculator import n

n(5)       # → 5 (present)
n(5)(0)    # → 5(0) (absent) — closest to writing 5(0) directly
n(5)(1)    # → 5 (stays present)
n(3)(5)    # → 15 (multiplier — 3 × 5)
n(10)(0)   # → 10(0) (absent)

# Build vectors naturally
vec = [n(10)(0), n(20), n(30)(0), n(40), n(50)(0)]
# → [10(0), 20, 30(0), 40, 50(0)]
```

You can also use the full form: `AbsentNumber(value, absence_level)`.

## Quick Start

```python
from absence_calculator import n, add, subtract, multiply, divide, erase, format_result

# Create numbers — present (default) or absent
five = n(5)        # 5 (present)
three_absent = n(3)(0)  # 3(0) (absent)

# Addition — same state combines, mixed state is unresolved
result = add(n(5), n(3))
print(result)  # 8

# Subtraction — equal values cancel to void
result = subtract(n(7), n(7))
print(result)  # void

# Multiplication — states combine (like XOR)
result = multiply(n(5)(0), n(3))
print(result)  # 15(0)

# Erasure — flips the state of the erased portion
result = erase(n(5), n(3))
print(result)  # 2 + 3(0)

# Over-erasure — excess becomes erased debt
result = erase(n(7), n(10))
print(result)  # 7(0) + erased 3

# Resolve erased excess by adding
resolved = add(result, n(3))
print(resolved)  # 10(0)

# Division by zero — defined! (0 is one absence)
result = divide(n(10), n(1)(0))
print(result)  # 10(0)
```

## Using the Expression Solver

```python
from absence_calculator import solve, format_result

# Parse and solve string expressions
print(format_result(solve("5 + 3")))           # 8
print(format_result(solve("5(0) + 3(0)")))     # 8(0)
print(format_result(solve("7 - 7")))           # void
print(format_result(solve("5(0) * 3")))        # 15(0)
print(format_result(solve("5 erased 3")))      # 2 + 3(0)
print(format_result(solve("7 erased 10")))     # 7(0) + erased 3 (over-erasure)
print(format_result(solve("5(0)(0)")))         # 5 (double absence = present)

# Parenthesized expressions (operations on unresolved inputs)
print(format_result(solve("(1 + 5(0)) erased 1")))  # 6(0)

# Zero operations
print(format_result(solve("0 + 0")))           # 2(0) (two absences)
print(format_result(solve("0 * 0")))           # 1 (absence of absence = presence)
print(format_result(solve("10 * 0")))          # 10(0)
print(format_result(solve("10 / 0")))          # 10(0)
```

## Interactive Calculator

After installing, you can run the interactive calculator from the command line:

```bash
phantomtrace
```

Or as a Python module:

```bash
python -m absence_calculator
```

This gives you a `calc >>` prompt where you can type expressions and see results.

## Core Concepts

### Objects and States

An object is a number that has both a **value** and a **state**:
- **Present** (default): Written normally, e.g. `5`. Present quantities reflect the presence of a given unit of interest. (e.g. if the unit is a cat, then 5 represents 5 cats that are there or in a present state)
- **Absent**: Written with `(0)`, e.g. `5(0)` — think of it as `5 * 0`. Absent quantities reflect the absence of a given unit of interest. (e.g. if the unit is a phone, then 5(0) represents 5 phones that are not currently there but are still considered for computation)

Both states carry magnitude. `5` and `5(0)` both have a value of 5 — the state tells you whether it's present or absent, but the magnitude never disappears.

### Absence
  
- **Zero**: `0` is not emptiness, it's one absence (`1(0) = 1 * 0 = 0`)
- **Absence of absence** returns to present: `5(0)(0) = 5`, and `0(0) = 1`

### Operations

| Operation | Symbol | Rule |
|-----------|--------|------|
| Addition | `+` | Expands the amount of objects under consideration. Same state: magnitudes combine. Mixed: unresolved |
| Subtraction | `-` | Contracts the amount of objects under consideration. (If the domain of consideration is constricted to nothing then the result is void. Void is not an object, nor the new zero, it simply means we are not considering anything on which to act.) Same state: magnitudes reduce. Mixed: unresolved|
| Multiplication | `*` | Magnitudes multiply. States combine (present*present=present, absent*present=absent, absent*absent=present) |
| Division | `/` | Magnitudes divide. States combine same as multiplication. Division by 0 is defined! |
| Erasure | `erased` | Same state required. Remainder keeps state, erased portion flips state. Over-erasure creates erased excess |

### Over-Erasure (v0.2.0)

When you erase more than the total, the result carries an **erased excess** (erasure debt):

- `7 erased 10` = `7(0) + erased 3` — all 7 flip state, 3 excess erasure persists
- Adding resolves excess: `(7(0) + erased 3) + 3` = `10(0)`
- Erasing erased: `(erased 3) erased (erased 3)` = `erased 3(0)` (absence of erased)

### Compound Expressions (v0.2.0)

Operations can now accept unresolved expressions as inputs:

- `(1 + 5(0)) erased 1` = `6(0)` — erases the present part, combining with the absent part

### Result Types

- **AbsentNumber**: A number with a state (present or absent)
- **Void**: Complete cancellation — not zero, but the absence of any quantity under consideration
- **ErasureResult**: Two parts — remainder (keeps state) and erased portion (flipped state)
- **ErasedExcess**: Excess erasure debt that persists until resolved
- **Unresolved**: An expression that cannot be simplified (e.g., adding present + absent)

## Toggle Module

The toggle module flips states of elements in vectors, matrices, and tensors using pattern-based index selection.

### Core Toggle Operations

- `toggle.where(pattern, range, data)` — flip elements **at** pattern-computed indices
- `toggle.exclude(pattern, range, data)` — flip everything **except** pattern-computed indices
- `toggle.all(data)` — flip **every** element at any depth

The **pattern** is a function (or string expression) that's evaluated across all whole numbers. The **range** is an output filter — only results that fall within `(start, end)` become target indices. The function determines the shape of the pattern; the range determines how far it reaches.

### Vectors — Present

```python
from absence_calculator import toggle, n

# Present vector — all elements start as present
vec = [10, 20, 30, 40, 50]

# x*2 produces 0, 2, 4, 6, 8... — range (0, 4) keeps outputs 0 through 4
# Hits: indices 0, 2, 4 (the even positions)
toggle.where(lambda x: x * 2, (0, 4), vec)
# → [10(0), 20, 30(0), 40, 50(0)]    targets flipped to absent

toggle.exclude(lambda x: x * 2, (0, 4), vec)
# → [10, 20(0), 30, 40(0), 50]       non-targets flipped to absent

toggle.all(vec)
# → [10(0), 20(0), 30(0), 40(0), 50(0)]  everything flipped to absent
```

### Vectors — Absent

```python
# Absent vector — all elements start as absent
vec = [n(10)(0), n(20)(0), n(30)(0), n(40)(0), n(50)(0)]

toggle.where(lambda x: x * 2, (0, 4), vec)
# → [10, 20(0), 30, 40(0), 50]       targets flipped back to present

toggle.exclude(lambda x: x * 2, (0, 4), vec)
# → [10(0), 20, 30(0), 40, 50(0)]    non-targets flipped back to present

toggle.all(vec)
# → [10, 20, 30, 40, 50]             everything flipped back to present
```

### Vectors — Mixed

```python
# Mixed vector — some present, some absent
vec = [n(10), n(20)(0), n(30), n(40)(0), n(50)]

toggle.where(lambda x: x * 2, (0, 4), vec)
# → [10(0), 20(0), 30(0), 40(0), 50(0)]  targets flip (present→absent, absent→present)

toggle.exclude(lambda x: x * 2, (0, 4), vec)
# → [10, 20, 30, 40, 50]                  non-targets flip

toggle.all(vec)
# → [10(0), 20, 30(0), 40, 50(0)]         every element flips its state
```

### String Patterns and Single Index

```python
# String pattern — "x^2" computes target indices
toggle.where("x^2", (1, 4), [4, 7, 19, 22, 26])
# → [4, 7(0), 19, 22, 26(0)]   indices 1 (1²) and 4 (2²) toggled

# Single index — use pattern "x" with range (i, i)
toggle.where("x", (2, 2), [10, 20, 30, 40, 50])
# → [10, 20, 30(0), 40, 50]    only index 2 toggled
```

### Matrices — Present

```python
# Present matrix — toggle.all flips every element in every row
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

toggle.all(matrix)
# → [[0, 2(0), 3(0)],
#    [4(0), 5(0), 6(0)],
#    [7(0), 8(0), 9(0)]]

toggle.where("x", (0, 0), matrix)
# → [[0, 2, 3],        index 0 toggled in each row
#    [4(0), 5, 6],
#    [7(0), 8, 9]]

toggle.exclude("x", (0, 0), matrix)
# → [[1, 2(0), 3(0)],  everything except index 0 toggled
#    [4, 5(0), 6(0)],
#    [7, 8(0), 9(0)]]
```

### Matrices — Absent

```python
# Absent matrix
matrix = [[n(1)(0), n(2)(0)], [n(3)(0), n(4)(0)]]

toggle.all(matrix)
# → [[1, 2],     everything flipped back to present
#    [3, 4]]
```

### Matrices — Mixed

```python
# Mixed matrix — rows have different states
matrix = [[n(10)(0), n(20), n(30)(0)],
          [n(40), n(50)(0), n(60)]]

toggle.where("x", (1, 1), matrix)
# → [[10(0), 20(0), 30(0)],   index 1 toggled in each row
#    [40, 50, 60]]

toggle.all(matrix)
# → [[10, 20(0), 30],         every element flips
#    [40(0), 50, 60(0)]]
```

## Tensor Creation (v0.5.0)

`tensor()` creates multi-dimensional tensors — nested lists of AbsentNumbers at any depth. Vectors are rank 1, matrices are rank 2, and you can go as deep as you need. Every element always retains both its value and its state — nothing is ever removed, only toggled.

```python
from absence_calculator import tensor, n

# Vector (rank 1) — 5 elements, all absent
v = tensor(5, fill='absent')
# → [1(0), 2(0), 3(0), 4(0), 5(0)]
# Default: values are sequential — position IS identity

# Matrix (rank 2) — 3 rows of 4 elements, all present
m = tensor((3, 4), fill='present')
# → [[1, 2, 3, 4],
#    [1, 2, 3, 4],
#    [1, 2, 3, 4]]

# 3D Tensor (rank 3) — 2 matrices of 3 rows of 4 elements
t = tensor((2, 3, 4), fill='absent')
# Each element has a value and a state — absent, but never gone

# 4D Tensor (rank 4)
t4 = tensor((2, 2, 3, 5))
# Matrices inside matrices — as deep as you need

# Random seed — fills with random scalar values instead of sequential
r = tensor(5, fill='present', seed=42)
# → [655, 115, 26, 760, 282]
# Same seed always gives the same values

r2 = tensor((2, 3), fill='absent', seed=7)
# → [[332(0), 971(0), 155(0)],
#    [405(0), 667(0), 50(0)]]
```

### Inspecting Tensors

```python
toggle.rank(n(5))                       # → 0 (scalar)
toggle.rank([n(1), n(2)])               # → 1 (vector)
toggle.rank([[n(1), n(2)], [n(3), n(4)]])  # → 2 (matrix)
toggle.rank(tensor((2, 3, 4)))          # → 3 (3D tensor)

toggle.shape(tensor((3, 4)))            # → (3, 4)
toggle.shape(tensor((2, 3, 4)))         # → (2, 3, 4)
```

## Tensor Operations

All calculator operations now work on tensors. Both inputs must have the same shape — operations are applied element-by-element at every depth.

### Add on Tensors

```python
from absence_calculator import n, add

# Element-wise addition on vectors
result = add([n(7), n(8)(0), n(10), n(9)(0)],
             [n(4), n(3), n(7)(0), n(9)])
# → [11, 8(0) + 3, 10 + 7(0), 9(0) + 9]
# Each position follows the same add rules: same state combines, mixed is unresolved

# Works on matrices too
result = add([[n(1), n(2)], [n(3), n(4)]],
             [[n(5), n(6)], [n(7), n(8)]])
# → [[6, 8], [10, 12]]
```

### Combine (v0.5.0)

`combine()` counts present vs absent at each position, ignoring scalar values. Each element becomes `1` (if present) or `1(0)` (if absent), then adds them together.

```python
from absence_calculator import n, combine

# Mixed states — each position has one present and one absent
combine([n(1), n(2)(0), n(3), n(4)(0)],
        [n(0), n(2), n(3)(0), n(4)])
# → [1 + 0, 0 + 1, 1 + 0, 0 + 1]
# (0 here is 1(0) — one absence)

# Both present at every position
combine([n(5), n(3)], [n(2), n(7)])
# → [2, 2]    (two presents each)

# Both absent at every position
combine([n(5)(0), n(3)(0)], [n(2)(0), n(7)(0)])
# → [2(0), 2(0)]    (two absents each)
```

### Compare (v0.5.0)

`compare()` measures the shift in present vs absent from tensor 1 to tensor 2. Most useful on already-combined tensors where each position has the same total magnitude split between present and absent.

Works directly with combine output — no need to manually construct Unresolved expressions:

```python
from absence_calculator import n, combine, compare

# Two pairs of tensors with different state patterns
c1 = [n(1), n(2), n(3), n(4)(0)]     # P P P A
c2 = [n(5), n(6), n(7), n(8)]        # P P P P
state1 = combine(c1, c2)
# → [2, 2, 2, 0 + 1]  (2P, 2P, 2P, 1A+1P)

c3 = [n(1)(0), n(2), n(3)(0), n(4)]  # A P A P
c4 = [n(5), n(6)(0), n(7)(0), n(8)]  # P A A P
state2 = combine(c3, c4)
# → [0 + 1, 1 + 0, 2(0), 2]  (1A+1P, 1P+1A, 2A, 2P)

compare(state1, state2)
# → [0, 0, 2(0), 1]
# Position 0: 2 present → 1 present = lost 1 → 0 (which is 1(0))
# Position 1: 2 present → 1 present = lost 1 → 0 (which is 1(0))
# Position 2: 2 present → 0 present = lost 2 → 2(0)
# Position 3: 1 present → 2 present = gained 1 → 1

# No change returns void
same1 = combine([n(1), n(2)(0)], [n(3)(0), n(4)])  # 1+0, 1+0
same2 = combine([n(5)(0), n(6)], [n(7), n(8)(0)])  # 1+0, 1+0
compare(same1, same2)
# → [void, void]  (no shift at either position)
```

## Toggle Module

The toggle module flips states of elements in vectors, matrices, and tensors using pattern-based index selection.

### Toggling at Any Depth

`toggle.all()` works at every depth — flips every element regardless of how deeply nested:

```python
from absence_calculator import tensor, toggle

# All works on vectors, matrices, tensors, anything
t = tensor((2, 3, 4), fill='present')
t_flipped = toggle.all(t)
# Every element in the entire 2×3×4 structure is now absent
# Values preserved — only states changed
```

### Axis-Aware Toggling (v0.4.0)

`where()` and `exclude()` now accept an `axis` parameter to control which level toggling happens at:

```python
# Matrix 3×5, all absent
m = tensor((3, 5), fill='absent')

# Identity function, range (0, 2) — keeps outputs 0, 1, 2
result = toggle.where(lambda x: x, (0, 2), m, axis=-1)
# Row 0: P P P _ _
# Row 1: P P P _ _
# Row 2: P P P _ _

# x*2, range (0, 4) — keeps outputs 0 through 4, hits 0, 2, 4
result = toggle.where(lambda x: x * 2, (0, 4), m, axis=-1)
# Row 0: P _ P _ P
# Row 1: P _ P _ P
# Row 2: P _ P _ P
# Even indices only — the pattern controls the shape, the range controls the reach

# 3D tensor — toggling reaches the deepest vectors
t = tensor((2, 2, 4), fill='absent')
result = toggle.where(lambda x: x, (1, 2), t, axis=-1)
# Outputs in [1,2] → toggles indices 1 and 2 in every vector:
# [0][0]: _ P P _
# [0][1]: _ P P _
# [1][0]: _ P P _
# [1][1]: _ P P _
```

### Selecting Slices

`toggle.select()` pulls out a sub-structure along any axis. The result is still a valid tensor — one rank lower:

```python
m = [[n(1), n(2), n(3)],
     [n(4), n(5), n(6)],
     [n(7), n(8), n(9)]]

# Select row 1 (axis=0) — gives a vector
toggle.select(m, axis=0, index=1)
# → [4, 5, 6]

# Select column 2 (axis=1) — gives a vector
toggle.select(m, axis=1, index=2)
# → [3, 6, 9]

# 3D tensor — selecting axis=0 gives a matrix
t = [[[n(1), n(2)], [n(3), n(4)]],
     [[n(5), n(6)], [n(7), n(8)]]]
toggle.select(t, axis=0, index=0)
# → [[1, 2], [3, 4]]

# Selecting axis=2 gives a matrix of single values
toggle.select(t, axis=2, index=1)
# → [[2, 4], [6, 8]]
```

### Replacing Slices

`toggle.assign()` replaces a slice at a given position:

```python
m = [[n(1), n(2)], [n(3), n(4)]]
new_row = [n(10), n(20)]

result = toggle.assign(m, axis=0, index=0, value=new_row)
# → [[10, 20], [3, 4]]   row 0 replaced, row 1 unchanged
```

### Combining Across an Axis

`toggle.across()` applies a function element-by-element across one axis, combining sub-structures. Each result is still an AbsentNumber with its value and state:

```python
m = [[n(1), n(2)(0), n(3)],     # Row 0: P _ P
     [n(1)(0), n(2), n(3)]]     # Row 1: _ P P

def both_present(x, y):
    if x.is_present and y.is_present:
        return n(x.value)
    return n(x.value)(0)

toggle.across(m, axis=0, fn=both_present)
# → [1(0), 2(0), 3]
# Only index 2 is present in both rows
# Values and states preserved — nothing removed
```

### Counting

```python
v = [n(1), n(2), n(3)(0), n(4), n(5)(0)]
toggle.count_present(v)           # → 3 (three elements are present)

m = [[n(1), n(2)(0)], [n(3)(0), n(4)]]
toggle.count_present(m)           # → 2 (two elements present total)

toggle.present_indices(v)         # → [0, 1, 3] (which positions are present)
```

### Intersect and Union (Convenience)

Shorthand for common `across()` patterns — both preserve all values and states:

```python
a = [n(1), n(2), n(3)(0), n(4),    n(5)(0)]  # P P _ P _
b = [n(1), n(2)(0), n(3), n(4)(0), n(5)(0)]  # P _ P _ _

toggle.intersect(a, b)  # → P _ _ _ _  (present only where BOTH are present)
toggle.union(a, b)      # → P P P P _  (present where EITHER is present)

# These work at any depth — matrices, 3D tensors, etc.
# Equivalent to across() with AND/OR logic functions
```

### Backward Compatibility

`toggle.ys` = `toggle.where`, `toggle.nt` = `toggle.exclude` — old names still work.

All existing vector and matrix code works unchanged. The `axis` parameter defaults to `-1` (last axis), which matches the original behavior.

## API Reference

### Types

- `AbsentNumber(value, absence_level=0)` — A number with a state. `absence_level` 0 = present, 1 = absent. Callable: `num(0)` flips state, `num(1)` keeps state, `num(k)` multiplies
- `n(value, absence_level=0)` — Shorthand for creating AbsentNumbers: `n(5)` = present 5, `n(5)(0)` = absent 5
- `Void` / `VOID` — Represents complete cancellation
- `ErasureResult(remainder, erased)` — Result of an erasure operation
- `ErasedExcess(value, absence_level=0)` — Excess erasure debt from over-erasure
- `Unresolved(left, op, right)` — An expression that can't be simplified

### Calculator Functions

- `add(x, y)` — Add two values or tensors element-wise (supports compound inputs with excess resolution)
- `subtract(x, y)` — Subtract two AbsentNumbers
- `multiply(x, y)` — Multiply two AbsentNumbers
- `divide(x, y)` — Divide two AbsentNumbers
- `erase(x, y)` — Erase y from x (supports over-erasure and compound inputs)
- `solve(expr_string)` — Parse and evaluate a string expression (supports parentheses)
- `format_result(result)` — Convert any result to a readable string
- `parse_number(s)` — Parse a string like `"5(0)"` into an AbsentNumber

### Special Functions (v0.5.0)

- `combine(x, y)` — Count present vs absent at each position (ignores scalar values, returns state counts)
- `compare(x, y)` — Measure the shift in present vs absent from tensor 1 to tensor 2

### Tensor Creation (v0.5.0)

- `tensor(shape, fill='absent', seed=None)` — Create a tensor of any shape. Sequential values by default, random values with a seed

### Toggle — Core

- `toggle.where(pattern, range, data, axis=-1)` — Toggle elements at pattern-computed indices along a specific axis
- `toggle.exclude(pattern, range, data, axis=-1)` — Toggle all elements NOT at pattern-computed indices along a specific axis
- `toggle.all(data)` — Flip the state of every element at any depth

### Toggle — Tensor Inspection

- `toggle.rank(data)` — Detect depth of nesting (0=scalar, 1=vector, 2=matrix, 3+=tensor)
- `toggle.shape(data)` — Return dimensions as a tuple
- `toggle.select(data, axis, index)` — Extract a slice along an axis (result is one rank lower)
- `toggle.assign(data, axis, index, value)` — Replace a slice at a given position
- `toggle.across(data, axis, fn)` — Combine elements across an axis using a function
- `toggle.count_present(data, axis=None)` — Count present elements (total or along an axis)
- `toggle.present_indices(vector)` — Return which positions are present in a vector
- `toggle.intersect(a, b)` — Present only where both inputs are present (convenience for across with AND)
- `toggle.union(a, b)` — Present where either input is present (convenience for across with OR)
- `toggle.ys` / `toggle.nt` — Backward-compatible aliases for `where` / `exclude`

### Constants

- `ALL_OPERATIONS` — Dictionary describing all operations with rules and examples

## License

MIT
