Building Expressions: The C / P / S Hierarchy
Real quantum operators aren’t single Paulis — they’re sums of products of operators, each with a coefficient. FockMap represents this with three nested types. Think of them as atoms → words → sentences:
| Type | Role | Analogy |
|---|---|---|
C<'T> |
Coefficient × single operator | A letter with emphasis |
P<'T> |
Product of operators | A word (ordered sequence) |
S<'T> |
Sum of products | A sentence (the whole expression) |
These types are generic — they work with any operator type, not just Paulis.
C — A single weighted operator
// Create X with coefficient 1:
let one_x = C<Pauli>.Apply X // 1 · X
// Create 0.5 · Y:
let half_y = C<Pauli>.Apply(Complex(0.5, 0.0), Y)
printfn "%O" one_x // "X"
printfn "%O" half_y // "(0.5 Y)"
P — An ordered product (tensor product)
When you multiply two C values you get a P — a product of operators
that represents a multi-qubit string:
// Build X ⊗ Y by multiplying two C values:
let xy = one_x * half_y
// P<Pauli> with Coeff = 0.5, Units = [X; Y]
// Or build directly from an array:
let xzy = P<Pauli>.Apply [| X; Z; Y |]
// 1 · (X ⊗ Z ⊗ Y)
// Products compose by concatenation:
let big = xzy * xzy
// X ⊗ Z ⊗ Y ⊗ X ⊗ Z ⊗ Y
// Scale the coefficient:
let scaled = xzy.ScaleCoefficient(Complex(3.0, 0.0))
// 3 · (X ⊗ Z ⊗ Y)
Reduction normalises all internal coefficients into the single overall coefficient — critical for comparing and combining terms:
let mixed = P<Pauli>.Apply(Complex(2.0, 0.0), [| half_y; one_x |])
// Coeff = 2.0, Units = [(0.5 Y); X]
let clean = mixed.Reduce.Value
// Coeff = 1.0, Units = [Y; X] — the 2 × 0.5 folded together
S — A sum of products (the Hamiltonian shape)
Most quantum operators are sums of terms. S<'T> collects product
terms and automatically combines like terms:
// Two terms:
let s1 = S<Pauli>.Apply(P<Pauli>.Apply [| X; Z |])
let s2 = S<Pauli>.Apply(P<Pauli>.Apply [| Y; I |])
// Add them: H = 1·(X⊗Z) + 1·(Y⊗I)
let hamiltonian = s1 + s2
// Like terms combine automatically:
let doubled = s1 + s1
// → 2·(X⊗Z)
// Multiplication distributes: (A + B)(C + D) = AC + AD + BC + BD
let distributed = (s1 + s2) * (s1 + s2)
You can inspect the terms:
for term in hamiltonian.ProductTerms.Value do
printfn "%O" term
// [X | Z]
// [Y | I]
How like-term combination works
S<'T> stores its terms in a Map<string, P<'T>>, keyed by the string
representation of the product’s operator sequence. When two terms share the
same key, their coefficients are summed automatically. This is what makes
s1 + s1 produce 2·(X⊗Z) instead of two separate entries.
Coefficient hygiene
Every level has a Reduce method that sanitizes coefficients. NaN and
infinity values are replaced with zero, preventing numerical corruption
from silently propagating through your computation:
member this.Reduce = { this with Coeff = this.Coeff.Reduce }
Zero propagation
A product containing any zero-coefficient unit becomes the zero product. This is checked eagerly so that downstream code never wastes time on terms that contribute nothing:
member this.IsZero =
(not this.Coeff.IsNonZero) ||
(this.Units |> Seq.exists (fun item -> item.IsZero))
Why this matters: Every Hamiltonian in quantum chemistry is an S<'T>.
The type system ensures you can’t accidentally mix up a single term with
a full expression.
Next: Operators on Specific Qubits — tagging operators with qubit indices