Metadata-Version: 2.4
Name: PhantomTrace
Version: 0.9.3
Summary: PhantomTrace — a mathematical framework where numbers exist in present or absent states with custom operations to include addition, subtraction, multiplication, division, and erasure.
Home-page: https://github.com/jordanbeitchman-spec/PhantomTrace
Author: PhantomTrace Project
License: MIT
Project-URL: Homepage, https://github.com/jordanbeitchman-spec/PhantomTrace
Project-URL: Documentation, https://github.com/jordanbeitchman-spec/PhantomTrace#readme
Project-URL: Issues, https://github.com/jordanbeitchman-spec/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: home-page
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.

**Home**: [GitHub Repository](https://github.com/jordanbeitchman-spec/PhantomTrace)

**Issues**: [GitHub Issues](https://github.com/jordanbeitchman-spec/PhantomTrace/issues)

**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
```


## Imports

```python
from phantomtrace import n, add, subtract, multiply, divide, erase
```

The `absence_calculator` module name is also supported for backward compatibility:

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


## 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 (can go negative). 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 |


## Number Creation

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

```python
from phantomtrace 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 phantomtrace 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

# Subtraction — can go negative
result = subtract(n(3)(0), n(5)(0))
print(result)  # -2(0) — negative absence is its own thing

# 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)
```

All operations accept plain Python integers — they are automatically treated as present numbers:

```python
from phantomtrace import add, subtract, multiply, divide, erase

add(n(5), 3)      # → 8
subtract(10, 3)   # → 7
multiply(n(5), 3) # → 15
divide(10, 2)     # → 5
erase(5, 3)       # → 2 + 3(0)
```


## Using the Expression Solver

```python
from phantomtrace import solve, format_result

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
print(format_result(solve("5(0)(0)")))         # 5 (double absence = present)

# Parenthesized expressions
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, 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.


## Erasure

Erasure is subtraction without forgetting the quantity removed. There are still the same number of objects present for computation before and after the application of erasure. Similarly to subtraction, over-erasure produces a debt-like quantity.

When you erase more than the total, the result carries **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)`

### Erasure and Subtraction: `erased()` and `negative()`

Just like subtraction can be thought of as adding a negative (`x - y = x + (-y)`), erasure can be thought of as adding an erased number: `erase(x, y) = x + erased(y)`. The `erased()` function creates an erased number, and the `erase()` function is shorthand for adding that erased state.

```python
from phantomtrace import n, erase, erased, negative, add

# erased() takes one input — the number you want to apply an erased state to
erased(n(5))       # → erased 5
erased(n(5)(0))    # → erased 5(0)

# erase(x, y) is equivalent to add(x, erased(y))
# Just like subtract(x, y) is equivalent to add(x, negative(y))
erase(n(5), n(3))              # → 2 + 3(0)
add(n(5), erased(n(3)))        # → 2 + 3(0) (same thing)

# Same state + same erasure level combine when added
add(erased(n(3)), erased(n(7)))        # → erased 10
add(erased(n(3)(0)), erased(n(7)(0))) # → erased 10(0)

# Adjacent levels resolve — erased(x) can erase x because x is there to erase
add(erased(n(5)), n(5))        # → 5(0)
add(erased(n(5)(0)), n(5)(0))  # → 5

# Two levels apart can't resolve — erased erased 5 needs erased 5, not plain 5
add(erased(erased(n(5))), n(5))          # → 5 + erased erased 5 (stays separate)
add(erased(erased(n(5))), erased(n(5)))  # → erased 5(0) (adjacent, resolves)

# negative() creates a negative number — same as normal math
negative(n(5))     # → -5
negative(n(5)(0))  # → -5(0)

# Both work on tensors element-wise
erased([n(1), n(2), n(3)])   # → [erased 1, erased 2, erased 3]
negative([n(1), n(2), n(3)]) # → [-1, -2, -3]
```

### Erasure Levels — `erased_n(x, n)`

`erased_n(x, n)` applies exactly `n` levels of erasure to `x` in a single call. Erasure levels are real numbers — integer, fractional, or negative.

```python
from phantomtrace import erased_n, multiply, divide, n

# Integer levels — same as calling erased() n times
erased_n(5, 1)    # → erased 5          (same as erased(5))
erased_n(5, 2)    # → erased erased 5   (same as erased(erased(5)))
erased_n(5, 0)    # → 5                 (zero erasure = identity, no change)

# Negative levels — inverse erasure (like 1/erased(x))
erased_n(5, -1)   # → erased^-1 5

# Fractional levels
erased_n(1, 0.5)  # → erased^0.5 1
```

Erasure levels **add during multiplication** and **subtract during division** — the same rule that makes `absent × absent = present`:

```python
# Half + half = full
h = erased_n(1, 0.5)
multiply(h, h)              # → erased 1   (0.5 + 0.5 = 1)

# Positive and negative cancel — result is a plain number, no erasure
e_pos = erased_n(3, 1)      # erased 3
e_neg = erased_n(3, -1)     # erased^-1 3
multiply(e_pos, e_neg)      # → 9   (1 + (-1) = 0, erasure gone, 3 × 3 = 9)

# 1/erased(x) — previously unresolved, now produces erased^-1
divide(n(1), erased_n(1, 1))  # → erased^-1 1
# Multiplying back cancels:
multiply(divide(n(1), erased_n(1,1)), erased_n(1,1))  # → 1
```

The general rule: when an erasure level reaches exactly 0 through any combination of multiplication and division, the result automatically returns to a plain `AbsentNumber` — absence of absence of erasure is presence.

### Layered Erasure

Erasure can be layered to any depth — you can erase erased quantities from other erased quantities. The same erasure rules apply: same state required, remainder keeps state, erased portion flips state.

**Important:** Double erasure is NOT like double negatives. A double negative gives you a positive (`-(-5) = 5`). But erasing an erased number gives you the *absence* of an erased number — and that only happens when an erased number of the same kind is actually present to be erased. The absence of an erased number is its own distinct thing, not a return to the original number. Erasure and subtraction are analogous (both remove quantity), but they behave differently when layered.

Each layer tracks its depth internally, so only quantities at the same erasure depth and same absence state can interact.

```python
from phantomtrace import n, erase, erased

# erase(erased(5), erased(3)) — erase "erased 3" from "erased 5"
# Remainder: erased 2 (unchanged). Erased portion: erased 3(0) — the absence of "erased 3"
erase(erased(n(5)), erased(n(3)))  # → erased 2 + erased 3(0)

# Full erasure — everything becomes the absence of its erased state
erase(erased(n(5)), erased(n(5)))  # → erased 5(0)

# Over-erasure — same rules apply
erase(erased(n(3)), erased(n(5)))  # → erased 3(0) + erased 2

# Works on absent-state erased quantities too
erase(erased(n(5)(0)), erased(n(3)(0)))  # → erased 2(0) + erased 3

# Mixed-state erased quantities can't erase each other
erase(erased(n(5)), erased(n(3)(0)))     # → unresolved (different states)

# Deeper layers work the same way
erased(erased(n(5)))             # → erased erased 5
erase(erased(erased(n(5))), erased(erased(n(3))))
# → erased erased 2 + erased erased 3(0)

# Different depths can't interact
erase(erased(erased(n(5))), erased(n(5)))
# → unresolved (different erasure depths)
```


## Compound Expressions

Operations work on unresolved expressions as inputs:

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

All five operations distribute over Unresolved expressions:

```python
from phantomtrace import n, add, subtract, multiply, divide

u = subtract(n(7), n(5)(0))    # → 7 - 5(0) (unresolved — different states)

add(u, 7)                      # → 14 - 5(0)
add(n(8)(0), u)                # → 7 + 3(0)
subtract(u, 2)                 # → 5 - 5(0)
multiply(u, n(5)(0))           # → 35(0) - 25
divide(u, n(5)(0))             # → 1.4(0) - 1
divide(n(5)(0), u)             # → 5(0) / 7 - 5(0)

multiply(divide(n(5)(0), u), 2)  # → 10(0) / 7 - 5(0)
```

Multiplication distributes because it is repeated addition. Division can be thought of as repeated subtraction up until void. Read Absence Theory for more information.


## 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. Created directly with `erased()`
- **Unresolved**: An expression that cannot be simplified (e.g., adding present + absent). Stores the full operation so it can be operated on in future computations


## Trace Function

`trace()` is an absent-aware lambda — it evaluates a function over a range of values, producing a vector of results. The range can be present or absent, and all operations work naturally within the trace.

### Trace Ordering

Numbers of different states (present vs absent) are the same magnitude, just different state — they cannot be ordered against each other by default. Same-state ranges iterate naturally by magnitude:

```python
from phantomtrace import n, trace, multiply

trace(lambda x: multiply(x, x), n(1), n(5))
# → [1, 4, 9, 16, 25] — present ascending

trace(lambda x: multiply(x, x), n(5)(0), n(2)(0))
# → [25, 16, 9, 4] — absent descending
```

Cross-state ranges require a user-defined ordering:

```python
from phantomtrace import n, trace, present, absent, largest, ordering

o = ordering(largest(present()), largest(absent()))

trace(lambda x: x, n(3), n(3)(0), order=o)
# → [3, 2, 1, 0, 2(0), 3(0)]
# Path: present descending → boundary → absent ascending

trace(lambda x: x, n(5)(0), n(5), order=o)
# → [5(0), 4(0), 3(0), 2(0), 0, 1, 2, 3, 4, 5]
```

Without an ordering, cross-state trace raises an error — the framework does not assume which state comes first.

### Basic Traces

```python
from phantomtrace import n, trace, multiply, add, erase, divide, subtract

# x² over an absent range
trace(lambda x: multiply(x, x), n(5)(0), n(2)(0))
# → [25, 16, 9, 4]   (absent × absent = present)

# x erased x — flips every value to the opposite state
trace(lambda x: erase(x, x), n(1), n(5))
# → [1(0), 2(0), 3(0), 4(0), 5(0)]

# x + x over a present range
trace(lambda x: add(x, x), n(1), n(4))
# → [2, 4, 6, 8]

# x - 1 over a range
trace(lambda x: subtract(x, n(1)), n(3), n(5))
# → [2, 3, 4]

# x / 2 over a range
trace(lambda x: divide(x, n(2)), n(2), n(6))
# → [1, 1.5, 2, 2.5, 3]

# Mixed operations: x² + x over absent range
trace(lambda x: add(multiply(x, x), x), n(1)(0), n(3)(0))
# → [1 + 0, 4 + 2(0), 9 + 3(0)]
# x² is present (absent × absent), x is absent → unresolved at each position
```

### Unbound and Partial Traces

You can create a trace without specifying the range and bind it later. `bind()` returns a new result and does not modify the original trace:

```python
# Unbound — define the function now, bind the range later
t = trace(lambda x: multiply(x, x))
print(t)              # trace(unbound)
t(n(5))               # → 25 (call it like a function)
result = t.bind(n(1), n(5))  # → [1, 4, 9, 16, 25]

# Partially bound — set start now, end later
t2 = trace(lambda x: add(x, n(10)), start=n(1))
result = t2.bind(n(3))       # → [11, 12, 13]
result = t2.bind(end=n(3))   # → [11, 12, 13]
```

### Void Rejection

Trace rejects void ranges — void means no number over which to operate:

```python
from phantomtrace import VOID

trace(lambda x: x, VOID, n(5))
# → ValueError: Cannot trace over void — void is no calculation
```


## Builder Module

The builder module lets you define your own mathematical domains with custom states, state transitions, and operation rules:

```python
from phantomtrace import StateSpace

space = StateSpace("quantum")
superposed = space.add_state("superposed")
collapsed  = space.add_state("collapsed")
entangled  = space.add_state("entangled")

space.add_transition("measure", "superposed", "collapsed")
space.add_transition("entangle", "collapsed", "entangled")

space.add_rule("add", same_state="combine", mixed_state="unresolved")
space.add_rule("multiply", state_combination={
    ("superposed", "superposed"): "collapsed",
    ("superposed", "collapsed"): "entangled",
    ("collapsed",  "collapsed"): "collapsed",
})

x = space.number(5, "superposed")
y = space.number(3, "superposed")
z = space.number(7, "collapsed")

space.add(x, y)           # → 8[superposed]
space.add(x, z)           # → unresolved
space.multiply(x, y)      # → 15[collapsed]
space.multiply(x, z)      # → 35[entangled]
space.transition("measure", x)  # → 5[collapsed]
```

The existing present/absent system can be expressed as a StateSpace:

```python
from phantomtrace import presence_absence_space

pa = presence_absence_space()
p5 = pa.number(5, "present")
a3 = pa.number(3, "absent")
pa.multiply(p5, a3)  # → 15[absent]
pa.multiply(a3, a3)  # → 9[present] (absence of absence = presence)
```


## Tensor Creation

`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 needed. Every element always retains both its value and its state — nothing is ever removed, only toggled.

```python
from phantomtrace 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)]

# 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)
t = tensor((2, 3, 4), fill='absent')

# 4D Tensor (rank 4)
t4 = tensor((2, 2, 3, 5))
```

### Seed — State Randomization

The `seed` parameter randomizes which positions are present vs absent. Values stay sequential (position = identity) — only states change. Closer seeds produce more similar patterns, with adjacent seeds differing by exactly one position.

```python
r0 = tensor(5, seed=0)
# → [1(0), 2(0), 3(0), 4(0), 5(0)]  (seed 0 = all absent)

r3 = tensor(5, seed=3)
# → 3 positions present, 2 absent (deterministic)

r4 = tensor(5, seed=4)
# → 4 positions present — differs from seed 3 by exactly 1 position

r5 = tensor(5, seed=5)
# → [1, 2, 3, 4, 5]  (seed = size = all present)

# Same seed always gives the same pattern
tensor(5, seed=3) == tensor(5, seed=3)  # True

# Works on matrices and higher — ordering spans the entire tensor
m = tensor((3, 4), seed=6)
# 6 of 12 positions are present, rest absent
```

### Inspecting Tensors

```python
from phantomtrace import toggle, n

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 work on tensors — add, subtract, multiply, divide, and erase. Both inputs must have the same shape. Operations are applied element-by-element at every depth.

```python
from phantomtrace import n, add, subtract, multiply, divide, erase

# Addition — same state combines, mixed is unresolved
add([n(7), n(8)(0), n(10)], [n(4), n(3), n(7)(0)])
# → [11, 8(0) + 3, 10 + 7(0)]

# Subtraction — equal values cancel to void
subtract([n(7), n(5), n(10)(0)], [n(3), n(5), n(4)(0)])
# → [4, void, 6(0)]

# Multiplication — states combine (absent × absent = present)
multiply([n(3), n(4)(0), n(2)(0)], [n(5), n(3), n(6)(0)])
# → [15, 12(0), 12]

# Division — magnitudes divide, states combine
divide([n(10), n(9)(0), n(8)], [n(2), n(3)(0), n(4)])
# → [5, 3, 2]

# Erasure — flips state of erased portion at each position
erase([n(7), n(5), n(3)], [n(3), n(5), n(1)])
# → [4 + 3(0), 5(0), 2 + 0]

# All operations work on matrices and higher-rank tensors
add([[n(1), n(2)], [n(3), n(4)]], [[n(5), n(6)], [n(7), n(8)]])
# → [[6, 8], [10, 12]]
```


## Special Functions

### Combine

`combine()` counts present vs absent at each position, ignoring scalar values. Each element becomes `1` (if present) or `1(0)` (if absent), then those are added together. Void elements contribute nothing.

```python
from phantomtrace import n, combine, VOID

# 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]

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

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

# Combine with void — counts only the non-void side
combine([n(5), n(4)(0), n(3), n(2)(0)], [VOID, VOID, VOID, VOID])
# → [1, 0, 1, 0]

# Void + void = void
combine([VOID, VOID], [VOID, VOID])
# → [void, void]
```

### Compare

`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.

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

c1 = [n(1), n(2), n(3), n(4)(0)]
c2 = [n(5), n(6), n(7), n(8)]
state1 = combine(c1, c2)
# → [2, 2, 2, 0 + 1]

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

compare(state1, state2)
# → [0, 0, 2(0), 1]

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

### Join

`join()` concatenates two lists into one, preserving all elements and their states:

```python
from phantomtrace import n, join, VOID

a = [n(5)(0), n(7), VOID, n(9)]
b = [n(6), n(7)(0), n(4)]

join(a, b)
# → [5(0), 7, void, 9, 6, 7(0), 4]
```


## 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) evaluated across all whole numbers. The **range** is an output filter — only results that fall within `(start, end)` become target indices.

### Vectors — Present

```python
from phantomtrace import toggle, n

vec = [10, 20, 30, 40, 50]

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

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

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

### Vectors — Absent

```python
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]

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

toggle.all(vec)
# → [10, 20, 30, 40, 50]
```

### Vectors — Mixed

```python
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)]

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

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

### 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]
```

### Matrices — Present

```python
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

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

toggle.where("x", (0, 0), matrix)
# → [[1(0), 2, 3],
#    [4(0), 5, 6],
#    [7(0), 8, 9]]

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

### Matrices — Absent

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

toggle.all(matrix)
# → [[1, 2],
#    [3, 4]]
```

### Matrices — Mixed

```python
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)],
#    [40, 50, 60]]

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

### Toggling at Any Depth

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

```python
from phantomtrace import tensor, toggle

t = tensor((2, 3, 4), fill='present')
t_flipped = toggle.all(t)
# Every element in the entire 2×3×4 structure is now absent
```

### Axis-Aware Toggling

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

```python
from phantomtrace import tensor, toggle

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

# Identity function, range (0, 2) — toggles first 3 columns in each row
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) — hits even indices only
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

# 3D tensor — reaches the deepest vectors
t = tensor((2, 2, 4), fill='absent')
result = toggle.where(lambda x: x, (1, 2), t, axis=-1)
# Toggles indices 1 and 2 in every innermost 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 one rank lower:

```python
from phantomtrace import n

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

toggle.select(m, axis=0, index=1)  # → [4, 5, 6]   (row 1)
toggle.select(m, axis=1, index=2)  # → [3, 6, 9]   (column 2)
```

### Counting and Querying

```python
from phantomtrace import toggle, n, tensor

v = [n(1), n(2)(0), n(3), n(4)(0), n(5)]

toggle.count_present(v)  # → 3
toggle.where_present(v)  # → (array([0, 2, 4]),)
```


## License

MIT
