The Expression Tree

sft-wick uses a custom lightweight expression tree instead of relying on SymPy. All expression types are frozen dataclasses — immutable and hashable — with exact rational arithmetic via fractions.Fraction.

Design Philosophy

  • No external symbolic algebra dependency. The expression tree is self-contained in sft_wick.expressions.

  • Immutability. Once created, an expression cannot be mutated. This makes expressions safe to use as dictionary keys, in sets, and in concurrent code.

  • Exact arithmetic. Coefficients are represented as Rational numbers backed by fractions.Fraction, avoiding floating-point drift.

  • LaTeX rendering. Every expression type implements to_latex().

The Expr Base Class

All expression types inherit from Expr, which provides operator overloads:

  • expr + expr — creates a Sum

  • expr * expr — creates a Product

  • -expr — multiplies by \(-1\)

  • expr - expr — addition of negation

  • repr(expr) — delegates to to_latex()

Numeric: Rational

Rational stores exact rational numbers with automatic normalisation (positive denominator, reduced form):

from sft_wick.expressions import Rational, ZERO, ONE

r = Rational(3, 4)       # 3/4
r.to_latex()              # '\\frac{3}{4}'
r.is_zero                 # False
r.is_one                  # False
r.to_fraction()           # Fraction(3, 4)

# Arithmetic stays exact
Rational(1, 3) * Rational(2, 5)  # Rational(2, 15)

Constants: ZERO, ONE, MINUS_ONE.

Symbolic: Symbol

Symbol represents named tensors and coupling constants with optional indices and spatial arguments:

from sft_wick.expressions import Symbol

Symbol('F', ('i', 'j', 'k'))               # F_{ijk}
Symbol('K', ('i', 'j'), ('y_0', 'y_1'))    # K_{ij}(y_0, y_1)

Propagators

Propagator stores a two-point function:

from sft_wick.expressions import Propagator

c = Propagator('C', 'a', 'b', 'x', 'y')  # C_{ab}(x, y)
r = Propagator('R', 'a', 'b', 'x', 'y')  # R_{ab}(x, y)
  • kind'C' (correlation) or 'R' (response)

  • For R, the physical field is always on the left by convention.

  • For scalar fields, index_left and index_right are None.

Composite: Sum and Product

Sum and Product hold tuples of sub-expressions. Nested structures are automatically flattened at construction time:

from sft_wick.expressions import Sum, Product

# Sum(Sum(a, b), c) becomes Sum(a, b, c)
# Product(Product(a, b), c) becomes Product(a, b, c)

Wrappers: SumOverIndex and IntegralOver

These wrap an inner expression with a summation or integration:

from sft_wick.expressions import SumOverIndex, IntegralOver

# sum_{i=1}^{3} body
SumOverIndex('i', 3, body)

# integral dy body
IntegralOver('y', body)

They are inserted automatically by compute_moment() when vertices introduce internal indices or spatial variables.

Delta Functions

KroneckerDelta and DiracDelta represent component and spatial delta functions respectively. Their equality and hashing are order-independent: \(\delta_{ij} = \delta_{ji}\).

KroneckerDelta factors are inserted automatically by DiagramTerm.apply_diagonal whenever diag_R=True or diag_C=True constraints merge a pair of external (non-summation) indices: the simplifier collapses C[a, b] -> delta_{a, b} C[a, a] so that pinning the observable indices to different values via fixed_indices correctly returns 0. _eval_symbolic evaluates a KroneckerDelta to 1.0 when both indices resolve to the same integer (via index_map or as a literal numeric label), and 0.0 otherwise.

Immutability and Hashability

Because every expression is a frozen dataclass, you can use them freely as dictionary keys or in sets:

expr_set = {c, r, c}   # {C_{ab}(x,y), R_{ab}(x,y)}
expr_dict = {c: 1.0}   # works

Imaginary Unit: ImaginaryUnit

ImaginaryUnit represents the imaginary unit \(\mathrm{i}\), used in the response phase convention. A module-level constant I is provided:

from sft_wick import I, ImaginaryUnit

I.to_latex()          # '\\mathrm{i}'
I == ImaginaryUnit()  # True

The function apply_response_phase() multiplies each term by \((-\mathrm{i})^n\) where n counts the response propagators, implementing \(\langle\phi\,\psi\rangle = -\mathrm{i}\,R\).