The COTT Calculator is a Python/SymPy implementation of Traction Theory's algebra,
with a tkinter calculator GUI and phase-plot visualizer. It lives under /solver
and comprises roughly 2100 lines across six files.
The solver is built on two core principles inherited from Traction Theory: totality (all operations defined for all values) and reversibility (no information loss). These principles drive every design decision — from zero having a reciprocal, to the universal power-of-power rule that eliminates branch cuts.
| File | Purpose |
|---|---|
traction.py |
Core algebra engine: types, simplifier, projection, logarithms |
calculator.py |
GUI: parser, calculator pad, phase-plot visualizer, hover gauges |
evaluator.py |
Numeric traction evaluator: TractionValue class, hybrid + exact grid modes |
registry.py |
General-purpose function registry (category → name → object) |
projections/base.py |
Base class for projection plugins |
projections/complex_lie.py |
Default projection: 0^z → e−Wz |
projections/q_surface.py |
Q-norm surface projection for zero-power coordinates |
test_traction.py |
106 pytest tests validating all identities |
test_calculator.py |
42 smoke tests for the parser/evaluator |
traction.py)
Three custom SymPy Expr subclasses form the foundation.
They are not Symbols — they hook into SymPy's evaluation
via _eval_power, __mul__, and __truediv__.
| Class | Represents | Key behaviour |
|---|---|---|
Zero |
0 | Not absorbing. 0*w=1, 0^{-1}=w |
Omega |
ω | Reciprocal of zero. w*0=1, w^{-1}=0 |
Null |
∅ | Erasure element. Result of a-a=null |
Two additional classes handle unevaluated logarithms:
Log0 (base-0 logarithm)
and LogW (base-ω logarithm).
These are SymPy Function subclasses that stay symbolic
until projected to ℂ.
_eval_power)
The _eval_power hooks fire during SymPy's Pow construction,
so most identities are enforced eagerly — no simplifier call needed:
| Identity | Generalised form |
|---|---|
| 0^0 = 1 | |
| 0^{-n} = w^n | |
| 0^w = -1 | |
| 0^{0^n} = n | 0^{0^x} = x for any x |
| 0^{w^n} = -n | 0^{w^x} = -x for any x |
The generalisation beyond integers enables the resolve() identity-resolution
technique: wrapping any expression in 0^{0^{x}}
is a guaranteed no-op.
traction_simplify)
The simplifier is a recursive, bottom-up rewriter. It handles three expression types beyond the eager power rules:
Pow: Universal power-of-power: (v^a)^b = v^{a*b} for any base v, not just 0 and ω. This is what eliminates branch cuts — (x^2)^{1/2} = x, not |x|.
Mul:
All Zero and Omega factors are unified into a single base-0 exponent
using ωa = 0−a.
Exponents are summed:
0^a * 0^b = 0^{a+b},
0^a * w^b = 0^{a-b}.
After cancellation, the result is reconverted
(negative integer exponent → ω-base).
Additionally, definite zero-class elements absorb sign:
(-1)*0 = 0
because negative zero cannot exist
(0-0=null, not -0).
Add:
If the result is SymPy's S.Zero (from cancellation),
it is replaced with Null (the erasure element).
project_complex)
Projection maps traction expressions to ℂ. It is intentionally the last step in the pipeline — all traction simplification happens first, so branch cuts from classical complex arithmetic are avoided.
The core mapping is the Lie group exponential:
The structure constant W = √(−iπ) ≈ 1.253 − 1.253i, with |W| = √π. This single formula handles all base-0 powers:
| Traction | Projected | Verification |
|---|---|---|
| 0^0 | e0 = 1 | |
| 0^w | e−W² = eiπ = −1 | Consistent with 0^w = -1 |
| 0^{w/2} | eiπ/2 = i | The dyadic imaginary unit |
| 0^n (positive n) | e−nW, magnitude < 1 | Zero-class: decaying magnitude |
| 0^{-n} | enW, magnitude > 1 | Omega-class: growing magnitude |
A critical identity for projection: ω = W in exponent space. This is derived from the consistency equation:
This means: wherever ω appears inside an exponent or logarithm argument,
the projector substitutes ω → W before evaluating.
Without this, ω would project to ∞ (its standalone value),
producing zoo contamination.
Inverting the Lie exponential gives the logarithm projections:
The same ω → W substitution is applied to arguments
containing ω before the ln is evaluated.
The visualizer renders phase plots (domain colouring) of traction expressions over a 2D grid. The pipeline is designed to stay in the traction domain as long as possible, projecting to ℂ only at the final step.
Expressions use three variables with distinct roles:
| Variable | Role | Substitution |
|---|---|---|
| p | Horizontal grid coordinate | Always the real horizontal axis |
| q | Vertical grid coordinate | Always the real vertical axis |
| x | Projection's native unit | Defined per projection (see below) |
p and q are projection-agnostic — they always map directly to the grid axes. x is the projection's native coordinate, meaning the same expression 0^x has different geometric meaning depending on which projection is active:
| Projection | x = | Interpretation |
|---|---|---|
| Complex Lie | p + q·0^{w/2} | Complex plane |
| Q-surface | 0^{p + q*0^{w/2}} | Complex plane lifted into zero-power domain |
All three variables may appear in the same expression: p^2 + x uses both raw coordinates and the native unit.
Step 1 — Parse:
Recursive descent parser converts the input string to a SymPy expression
with traction types (Zero, Omega, etc.).
Step 2 — Substitute:
Replace p → a,
q → b (real grid symbols),
and x → the active projection's native_x(a, b).
This substitution happens in the traction domain —
the expressions are still traction objects, not complex numbers.
Step 3 — Traction Simplify: Apply all traction rewrite rules. The universal power-of-power rule fires here, collapsing expressions like (x^2)^{1/2} → x before any complex arithmetic occurs. This is what eliminates branch cuts.
Step 4 — Project: The active projection converts the simplified traction expression to a form suitable for numeric evaluation. For Complex Lie, this applies 0^z → e−Wz. For Q-surface, it decomposes zero-powers into phase and magnitude coordinates.
Step 5 — Evaluate: Three evaluator modes are available, selectable in the Settings panel:
| Evaluator | Method | Speed | Correctness |
|---|---|---|---|
| Fast (numpy) | SymPy lambdify → numpy vectorized |
Instant | May have branch cuts at singularities; p/q at q=0 gives NaN |
| Hybrid (TractionValue) | Per-pixel Python loop with class-tracked arithmetic | Seconds | Correct zero/omega classes; p/q at q=0 gives p·ω |
| Exact (SymPy) | Full traction_simplify + project_complex per pixel |
Minutes | Exact — uses the full symbolic engine for every grid point |
The hybrid evaluator introduces TractionValue (evaluator.py),
a numeric type that tracks whether a value is a plain scalar, a zero-power
(0^{a+b*w}), or an omega-scaled quantity (result of division by zero).
Each arithmetic operation preserves the class distinction:
| Operation | Class transition |
|---|---|
| p / 0 | SCALAR → OMEGA_VAL(p) |
| 0 ^ OMEGA_VAL(p) | → ZERO_POWER with ω-exponent → eiπp |
| ZERO_POWER × ZERO_POWER | Exponents add: 0^a * 0^b = 0^{a+b} |
| Scalar near 0 in base | Promoted to traction 0 |
The hybrid and exact evaluators run in a background thread, keeping the GUI responsive. A progress indicator shows completion percentage, and starting a new computation automatically cancels any in-progress one.
Step 6 — Colour: Two modes: Phase mode (CMYT quadrant colours, brightness by log-magnitude) and Continuity mode (CMYT following magnitude via double-cover arctan, ensuring |f|→0 and |f|→∞ have the same colour).
The key architectural decision: substitution and simplification happen in the traction domain, before any projection to ℂ. An earlier design projected first, then substituted:
expr → project → subs x→a+bi → numpy (branch cuts!)
This inherited all of ℂ's branch cut baggage. Fractional powers like (x^2)^{1/2} would hit numpy's principal-value convention, causing discontinuities on the imaginary axis. The native pipeline reverses the order:
expr → subs p,q,x → traction_simplify → project → evaluate
By the time the expression reaches numeric evaluation, the traction simplifier has already collapsed the nested powers. The branch cut never forms. And with the hybrid evaluator, even the numeric layer preserves traction semantics — division by zero produces ω, not NaN.
Projections are modular. Each projection lives in its own
.py file under projections/
and self-registers into the global registry on import.
registry.py)
A general-purpose {category, name} → object store.
Not limited to projections — any subsystem can register functions,
strategies, or configuration under its own category.
Create a file in projections/ that subclasses Projection
from projections/base.py. Implement two methods:
project_expr(traction_expr, a, b) —
Convert a traction expression (containing symbols a,
b) to a form suitable for lambdify.
Handles variable detection and substitution mode (single-variable vs two-variable).
eval_grid(projected_expr, a, b, AA, BB) —
Lambdify, evaluate on the numpy meshgrid, and return a dictionary of 2D arrays:
{Z, Re, Im, mag, log_mag, phase, brightness}.
Call self.register_self() at module level. The
projections/__init__.py auto-imports all .py files
in the directory, triggering registration.
projections/q_surface.py)
An alternative to the Lie exponential that maps zero-powers directly to coordinates on a q-norm unit surface. Given 0^t:
The native unit for q-surface is x = 0^{p + q*0^{w/2}} — the same complex plane input as Complex Lie, but lifted into the zero-power domain. This means the same expression (e.g. x^2) produces interesting zero-power structure on the q-surface while appearing as a standard polynomial on Complex Lie.
The parameter q selects the geometry of the surface:
| q | Norm | Surface shape |
|---|---|---|
| 1 | Taxicab | Diamond |
| 2 | Euclidean | Circle (default) |
| ∞ | Chebyshev | Square |
For |t| > 1, the magnitude formula takes a root of a negative number, which in traction algebra naturally rotates into the zero-power domain via 0^{w/2}. This gives the projection its projective character — values wrap through infinity rather than diverging.
The q-surface decomposer handles sums of zero-powers: each term in an
Add is decomposed independently, and the q-coordinates are
accumulated weighted by scalar factors. Expressions without zero-power
structure (e.g. plain x^2) produce a blank plot —
the projection is honest about what it can and cannot represent.
Zero and Omega are Expr subclasses
with _eval_power hooks. This means SymPy evaluates
0^w → −1 during construction
of the Pow object — no explicit simplify call needed.
Using plain Symbols would require a post-hoc rewriter for every expression.
In multiplication, all Zero and Omega factors
are converted to base-0 exponents
(ωa = 0−a),
summed, and reconverted. This single mechanism handles:
0*w=1 (exponents cancel),
0^2*w=0 (partial cancel),
0^a * 0^b = 0^{a+b} (symbolic combine),
and cross-base products like 0^5 * w^2 = 0^3.
Because the additive identity is erasure
(x - x = null),
not x + 0 = x,
the expression -0 = 0 - 0 = null.
Negative zero is incoherent in this system.
The parser collapses -0 → 0,
and the simplifier absorbs sign from definite zero-class elements:
(-1)*0 = 0,
(-2)*0 = 2*0.
sp_simplify
SymPy's simplify() is deliberately avoided in the projection pipeline.
It aggressively rewrites expressions in ways that break numeric evaluation —
for instance, merging i·√r into √(−r),
which produces NaN for positive r.
Projected expressions are left in their natural factored form.
SymPy eager evaluation:
0^1 is eagerly simplified to Zero()
by _eval_power, losing the exponent information.
This means Pow(Zero(), 1) and bare Zero() are
indistinguishable to the projector — both project via
e−W.
Non-analytic two-variable mode: When x and y are independent real variables, the function is generally not analytic in the complex sense. Phase and magnitude gradients are independent, so the tangent/normal lines (which follow magnitude) may not align with the phase colouring. This is correct behaviour, not a bug.
Projection completeness: The Lie exponential projection is one of potentially many valid mappings from traction to ℂ. The plugin system exists specifically to allow alternative projections (e.g. rational-magnitude formulas) to be developed and compared.