Skip to main content

Operator Transformations

At a glance

  • Operator transformations manipulate quantum expressions symbolically. >
  • Transformations can be split into two categories:
    1. General: Apply to any expression (e.g., algebraic simplification)
    2. Specific: Exploit operator properties (e.g., Pauli commutation rules)
  • All transformations have both in-place and out-of-place versions
  • Transformations are designed to prepare operator expressions for efficient numerical calculations
Prerequisites

Before working with transformations, you should be familiar with the operator sums and products that are generated from the factory functions operator_sum and operator_prod. We encourage you to review these types first to understand how operator expressions are structured and constructed.

Design Philosophy

Our transformation system follows a "nothing is automatic" principle - all operations are explicit, transparent, and user-controlled. This deliberate design decision ensures that:

  • Users have full visibility into each transformation step
  • Complex simplifications don't happen unexpectedly
  • The transformations remain predictable and debuggable
  • Users can selectively apply only the transformations they need

This approach prioritizes transparency, giving users complete control over how the expressions are processed.

Overview of transformations

When building quantum operator expressions through composition, the resulting expressions can become unwieldy with redundant terms, nested structures, and numerical artifacts. Operator transformations convert these expressions into cleaner and canonical forms that are ready for calculations. If no transformations are employed, no simplifications of the expressions are done.

Each operation serves a specific purpose:

  1. flatten expands nested products into a flat sum
  2. reorder_by_site sorts operators by site index for canonical ordering while obeying commutation relations
  3. merge combines identical operator terms
  4. prune removes negligible contributions.

In-place vs. out-of-place transformations

Every transformation comes in two versions. Out-of-place versions (merged, pruned, flattened) preserve the original expression and return a transformed copy, while in-place versions (merge, prune, flatten) modify it directly.

var cleaned = merged(original);  // original unchanged
merge(original); // original is modified

In Aleph, methods in the past tense return copies while those in the present tense denote in-place modification. Use in-place transformations when working with large expressions to save memory, and chain them for efficiency:

flatten(S);
merge(S);
prune(S);
note

Not all transformations have in-place versions. reorder_by_site and simplify_paulis always return new copies, so you must assign the result back to a variable:

var reorderS = reorder_by_site(S);
var simpleS = simplify_paulis(S);

Using transformations

Flatten nested structures

Flattening distributes multiplication over addition, expanding nested expressions into a flat sum of products. A generic flattened operator_sum SS is one that has the form

S=nMcnO1Omn, cnCS = \sum_n^M c_n O_1 \dots O_{m_n}, \ c_n \in \mathbb{C}

Such a sum has MM total terms, each with a possibly different number of non-nested operators OO in a product. Given some operator expression, the flatten function transforms it into the form shown above.

var S = operator_sum(2.0 * Z(0) * (X(1) + Y(2)), as_real)
flatten(S);
// Result: 2 * Z(0) * X(1) + 2 * Z(0) * Y(2)

In the above example a sum is made using operator_sum consisting of a single term 2Z0(X1+Y2)2Z_0(X_1 + Y_2). The flatten function then modifies the sum object in-place by expanding the sum into 2Z0X1+2Z0Y22Z_0X_1 + 2Z_0Y_2.

tip

Use flatten before doing any other kind of transformations as hidden duplicate terms and similat simplfications only become visible after flattening.

warning

Only use flatten on operator_sum types where you've built nested structures.

Flattening a single operator like X(0) or clear operator_prod type is pointless.

Merge like terms

One simplification we can perform on a flattened sum is to merge like terms. Merging identifies identical operators and combines their coefficients and removes terms near-zero coefficients (below machine precision).

var S = (2.0 + 1.0i)*X(0) + 3.0*Y(1) + 1.5*X(0)
merge(S); // Result: (3.5 + 1.0i)*X(0) + 3.0*Y(0)
tip

Merging before computing expectation values, time evolution, or other operations can increase performance from removing redundant terms.

Reorder by site

Before merging, it can be helpful to reorder operators within each term by their site indices. The reorder_by_site function sorts operators in ascending order of site index while preserving quantum commutation rules.

var S = 2.0 * (X(2) * Y(0)) + 3.0 * (Z(1) * X(0));
var reordered = reorder_by_site(S);
// Result: 2.0 * Y(0) * X(2) + 3.0 * X(0) * Z(1)

This reordering only occurs between operators with disjoint support (acting on different qubits), so quantum mechanical commutation relationships are preserved. For example, X(0) * Y(0) won't be reordered since both operators act on the same site.

Canonical reordering helps merge identify identical terms that differ only in operator order:

var S = X(1) * Y(0) + Y(0) * X(1);  // Same operators, different order
var reordered = reorder_by_site(S);
// Result: Y(0) * X(1) + Y(0) * X(1)
merge(reordered);
// Result: 2.0 * Y(0) * X(1)
tip

Use reorder_by_site before merge to ensure terms with the same operators in different orders are properly combined.

Prune negligible contributions

As operator_sum expressions get complex, terms may accrue small coefficients or the operators themselves may have small norms. By pruning the sum, terms whose effective norms fall below a threshold are removed. Here, the effective norm is computed as:

coefficient×operator norm|\text{coefficient}| \times \text{operator norm}

where operator norm\text{operator norm} represents the spectral norm, i.e., the absolute value of the largest eigenvalue of the operator.

var S = X(0) + 0.5*Y(1) + 1e-14*Z(2) + 1e-15*X(3);
prune(S); // Removes 1e-14*Z(2) and 1e-15*X(3)

Default thresholds are 101310^{-13} for double precision and 10410^{-4} for single precision.

Use prune(expr, tolerance) for custom cutoffs:

var S = 0.1*X(0) + 0.05*Y(1) + 1e-6*Z(2);
prune(S, 1e-5); // Removes 1e-6*Z(2) with custom tolerance
tip

Use prune after merging to catch near-zero coefficients created when terms nearly cancel.

warning

Be careful with custom prune tolerances. Setting the tolerance too high can remove physically important terms. Understand your physical scale before using custom tolerances.

Simplify Pauli operators

Special expressions can be manipulated and simplified utilizing known properties of the operators. In the case of expressions containing only Pauli operators, i.e. I,X,Y,ZI,X, Y, Z, simplify_paulis applies Pauli algebra rules. This results in the simplification of the pauli operators via the following identities,

X2=Y2=Z2=I, XY=iZ, YZ=iX, ZX=iYX^2 = Y^2 = Z^2 = I, \ XY = iZ, \ YZ = iX, \ ZX = iY
var chain = X(0) * Y(0);
var simplified = simplify_paulis(chain); // Returns i*Z(0)
warning

There is no in-place version for this function.

note

simplify_paulis always returns complex coefficients, even for real inputs. Pauli multiplication introduces imaginary phases (like XY=iZXY = iZ), so the result is always a complex operator_sum.

tip

When working with Pauli operators, apply the general transformations first, then use simplify_paulis at the end.

In the special case of an operator_sum comprised only of pauli operators, one can flatten, merge, and prune it to prepare the sum for simplify_paulis.

var term1 = X(0)*Y(0) + Z(1);  // Pauli products
var term2 = X(2) + Y(2);
var S = 2.0*term1*term2 + 3.0*X(0)*Y(0)*X(2) + 1e-15*Z(3); // Nested, duplicates, and noise

// Step 1: Flatten nested structures
flatten(S);
// Result: 2*X(0)*Y(0)*X(2) + 2*X(0)*Y(0)*Y(2) + 2*Z(1)*X(2) + 2*Z(1)*Y(2) + 3*X(0)*Y(0)*X(2) + 1e-15*Z(3)

// Step 2: Merge duplicate terms
merge(S);
// Result: 5*X(0)*Y(0)*X(2) + 2*X(0)*Y(0)*Y(2) + 2*Z(1)*X(2) + 2*Z(1)*Y(2) + 1e-15*Z(3)

// Step 3: Prune with custom tolerance
prune(S, 1e-10); // Removes 1e-15*Z(3)
// Result: 5*X(0)*Y(0)*X(2) + 2*X(0)*Y(0)*Y(2) + 2*Z(1)*X(2) + 2*Z(1)*Y(2)

// Step 4: Simplify Pauli products (XY -> iZ)
var final = simplify_paulis(S);
// Final: (0+5i) * Z(0) * X(2) + (0+2i) * Z(0) * Y(2) + (2+0i) * Z(1) * X(2) + (2+0i) * Z(1) * Y(2)

Compute commutators

Evaluating the commutator of two operators [A,B]=ABBA[A,B] = AB - BA is a fundamental operation in quantum mechanics. The commutator function returns an explicit calculation of the commutator of any two input operator expressions.

var comm = commutator(X(0), Y(0));  // Returns (1+0i)*X(0)*Y(0) + (-1+-0i)*Y(0)*X(0)

Like any given operator expresison, the output is not simplified. Using the general transformations above, expressions can be simplified and truncated, and in the case of Pauli matrices we can simplify even further.

var comm = commutator(X(1), Y(1));
// Returns (1+0i) * X(1) * Y(1) + (-1+-0i) * Y(1) * X(1)
simplify_paulis(comm);
// Returns (0+2i) * Z(1)

In the case of the operators being identical or have disjoint support, the commutator automatically returns an empty operator representing zero.

var iden = commutator(X(0), X(0)); 
// Returns ()
var disJ = commutator(X(0), X(1));
// Returns ()

This is useful for optimization - you can skip expensive commutator calculations for disjoint operators, or identify which operators commute with your system Hamiltonain or circuit.

Detailed documentation

To check out more detailed documentation see Transformations in the Library Reference.

Documentation Contributors

Eunji Yoo

Sebastien J. Avakian

Jonathon Riddell