Symbolic Sums and Indices
Many physical systems contain multiple identical elements with different parameters. Symbolic summation lets you write compact Hamiltonians with indexed operators and derive equations once, then instantiate for specific values later.
A canonical example is the Tavis-Cummings Hamiltonian describing $N$ two-level atoms coupled to a cavity mode:
\[H_\mathrm{TC} = \omega_c a^\dagger a + \sum_i^N \omega_i \sigma_i^{22} + \sum_i^N g_i \left(a^\dagger \sigma_i^{12} + a\, \sigma_i^{21}\right).\]
Earlier versions of SecondQuantizedAlgebra.jl shipped a ClusterSpace type — inherited from QuantumCumulants.jl, from which this package was refactored — that wrapped a single-site Hilbert space with a fixed number of identical copies and a correlation-truncation order. QuantumCumulants.jl has since deprecated that workflow in favor of indexed operators (see its indexed superradiant laser tutorial), and we have followed suit: ClusterSpace, cluster_expand, the integer-instantiation helper insert_index, and the cluster-aware to_numeric(op, b, ranges) overload have all been removed. The recommended way to handle many identical subsystems is to keep a plain Hilbert space and use a symbolic Index of range $N$ together with Σ. The number of copies stays symbolic through equation derivation, and the cumulant truncation order is supplied to the downstream solver (meanfield(...; order=k)) rather than being baked into the Hilbert space.
Index
An Index represents a symbolic summation variable. It is constructed from a Hilbert space, a name, a range (symbolic or numeric), and the subspace it acts on:
using SecondQuantizedAlgebra
@variables N
ha = NLevelSpace(:atoms, 2)
hc = FockSpace(:cavity)
h = hc ⊗ ha
i = Index(h, :i, N, ha) # index over the NLevelSpace
j = Index(h, :j, N, 2) # equivalent: specify subspace by positionIndexed operators
Associate an operator with an Index using IndexedOperator:
σ(x, y, z) = IndexedOperator(Transition(h, :σ, x, y, 2), z)
σ(2, 1, i)\[{\sigma}_{i}^{{21}}\]
Similarly, IndexedVariable creates symbolic parameters with an index:
gi = IndexedVariable(:g, i)\[ \begin{equation} g\left( i \right) \end{equation} \]
Concrete slots
After an index has been unrolled (e.g. by QuantumCumulants.evaluate), you often want to refer to a specific position. Calling an Index value with an Integer returns a fresh per-slot Index named Symbol(i.name, "_", k) with the same range and subspace. Pass it through IndexedOperator to build the concrete-site operator that matches the post-unroll naming convention:
σ(2, 1, i(3))\[{\sigma}_{i_3}^{{21}}\]
Summations
Use Σ (or equivalently ∑) to create a symbolic sum over an index:
Σ(σ(2, 2, i), i)\[\underset{i}{\overset{N}{\sum}}{\sigma}_{i}^{{22}}\]
A third argument specifies indices that are not equal to the summation index:
Σ(σ(2, 2, i), i, [j])\[\underset{i{\neq}j}{\overset{N}{\sum}}{\sigma}_{i}^{{22}}\]
Each non-equal pair carries meaning only where it can be observed: the summation index it filters, an operator carrying one of the paired indices, or a coefficient depending on one of them. Pairs failing that test (e.g. between two indices that appear nowhere in the term) are stripped automatically at construction. Callers building QAdds directly do not need to clean up term.ne manually. See the QAdd internals section of the developer documentation for the full invariant.
Diagonal splitting
When two indices acting on the same Hilbert space meet inside a product, the diagonal term (where both indices are equal) is automatically separated:
k = Index(h, :k, N, ha)
l = Index(h, :l, N, ha)
Σ(σ(2, 1, k) * σ(1, 2, l), k, l)\[\underset{l}{\overset{N}{\sum}}{\sigma}_{l}^{{22}} + \underset{k}{\overset{N}{\sum}} \underset{l,k{\neq}l}{\overset{N}{\sum}}{\sigma}_{k}^{{21}}{\sigma}_{l}^{{12}}\]
This also happens when a sum is multiplied by an indexed operator on the same space:
Σ(σ(2, 2, k), k) * σ(2, 1, l)\[{\sigma}_{l}^{{21}} + \underset{k{\neq}l}{\overset{N}{\sum}}{\sigma}_{k}^{{22}}{\sigma}_{l}^{{21}}\]
The diagonal term may produce a ground-state projector — for instance, the diagonal of $\sigma^{12}_k \cdot \sigma^{21}_l$ at $k=l$ is $\sigma^{11}_l$. Same-site composition that produces a ground-state projector triggers eager completeness expansion via $\sigma^{gg} = 1 - \sum_{k \neq g}\sigma^{kk}$, so the canonical basis stays $\{\sigma^{ij} : (i,j) \neq (g,g)\} \cup \{1\}$. Standalone $\sigma^{gg}$ built directly with Transition(h, :σ, g, g) stays atomic until it enters a *; you can also trigger the rewrite by hand via expand_completeness.
Free indices and assume_distinct_index
Two indexed operators on the same Hilbert subspace whose indices are both free (neither bound by a Σ) have an undetermined site relationship — the algebra cannot tell whether the user means "distinct atomic sites" or "two index variables that may coincide". The conservative default is the second: the pair stays in its physical order and no same-site collapse fires.
When you do want to assert that two free indices denote distinct sites, use assume_distinct_index. It augments every term's per-term inequality constraints with the supplied pairs, re-canonicalizes so the resolved pairs sort deterministically, and runs expand_completeness on any ground-state projectors that emerge from same-site composition under the new constraint:
ki = Index(h, :ki, N, ha)
li = Index(h, :li, N, ha)
assume_distinct_index(σ(1, 2, ki) * σ(2, 1, li), [(ki, li)])\[{\sigma}_{ki}^{{12}}{\sigma}_{li}^{{21}}\]
When at least one index is bound by a Σ, the diagonal-splitting machinery shown above handles the boundary case automatically and no explicit assumption is needed.
Example: Tavis-Cummings model
Putting it all together for $N$ two-level atoms in a cavity:
@variables Δ κ
@qnumbers a::Destroy(h, 1)
H = Δ * a' * a + Σ(gi * (a * σ(2, 1, i) + a' * σ(1, 2, i)), i)\[\Delta a^{\dagger}a + \underset{i}{\overset{N}{\sum}}\left( g\left( i \right) a{\sigma}_{i}^{{21}} + g\left( i \right) a^{\dagger}{\sigma}_{i}^{{12}} \right)\]