COTT Calculator

architecture guide

---
1. Overview

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.

1.1 File Map
Source code on Github
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^zeWz
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
---
2. Core Algebra (traction.py)
2.1 Custom Types

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

2.2 Power Rules (_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.

2.3 Simplification Engine (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 = 0a. 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).

---
3. Complex Projection (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.

3.1 The Lie Exponential

The core mapping is the Lie group exponential:

0^z eWz   where   W² = −iπ

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 eW² = eiπ = −1 Consistent with 0^w = -1
0^{w/2} eiπ/2 = i The dyadic imaginary unit
0^n (positive n) enW, magnitude < 1 Zero-class: decaying magnitude
0^{-n} enW, magnitude > 1 Omega-class: growing magnitude
3.2 Omega in Exponent Space

A critical identity for projection: ω = W in exponent space. This is derived from the consistency equation:

eW·tω = eiπt   ⇒   Wω = −iπ = W²   ⇒   ω = W

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.

3.3 Logarithm Projection

Inverting the Lie exponential gives the logarithm projections:

log0(y) −ln(y) / W
logω(y) ln(y) / W

The same ω → W substitution is applied to arguments containing ω before the ln is evaluated.

---
4. Visualization Pipeline

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.

4.1 The Native Pipeline
4.1.1 Variable System

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.

4.1.2 Pipeline Steps

Step 1 — Parse: Recursive descent parser converts the input string to a SymPy expression with traction types (Zero, Omega, etc.).

Step 2 — Substitute: Replace pa, qb (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^zeWz. 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).

4.2 Why Native Matters

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.

---
5. Projection Plugin System

Projections are modular. Each projection lives in its own .py file under projections/ and self-registers into the global registry on import.

5.1 Registry (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.

5.2 Writing a Projection

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.

5.3 Q-Surface Projection (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:

phase = t    magnitude = (1 − t²)1/q

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.

---
6. Design Decisions
6.1 Expr Subclasses, not Symbols

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.

6.2 Unified Base-0 Exponents

In multiplication, all Zero and Omega factors are converted to base-0 exponents (ωa = 0a), 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.

6.3 Negative Zero Cannot Exist

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 -00, and the simplifier absorbs sign from definite zero-class elements: (-1)*0 = 0, (-2)*0 = 2*0.

6.4 Avoiding 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.

---
7. Known Limitations

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

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.

---