Basis states and GT patterns

GT patterns are the basis states used by GroupFunctions.jl. Each one labels a single basis vector of an irrep; basis_states enumerates them, and the weight functions read off what a pattern encodes. For why GT patterns are the right basis, see the background page. Here we just build and use them.

We carry one example throughout: the irrep λ = [2, 0, 0] of U(3) — two bosons in three modes.

Build one directly

When you already know the pattern you want, construct it from its rows (top row first):

julia> using GroupFunctions
julia> gt = GTPattern([[2, 0, 0], [1, 0], [1]])│ 2 0 0 ╲ │ 1 0 〉 │ 1 ╱

Generate all patterns in an irrep

More often you want the whole basis. basis_states returns every pattern of the irrep, in a fixed order:

julia> λ = [2, 0, 0];
julia> basis = basis_states(λ);
julia> length(basis)6
julia> basis[1]│ 2 0 0 ╲ │ 0 0 〉 │ 0 ╱
julia> basis[2]│ 2 0 0 ╲ │ 1 0 〉 │ 0 ╱

Read the weights

A pattern's p-weight is its sequence of row-sum differences; occupation_number is the same data in mode order (it is reverse ∘ pweight). For the symmetric irrep [2,0,0] the occupation numbers are literally the Fock occupations:

julia> gt = basis[1];
julia> pweight(gt)3-element Vector{Int64}: 2 0 0
julia> occupation_number(gt)3-element Vector{Int64}: 0 0 2

Listing the occupation numbers across the basis recovers every Fock state of two photons in three modes:

julia> occupation_number.(basis)6-element Vector{Vector{Int64}}:
 [0, 0, 2]
 [0, 1, 1]
 [1, 0, 1]
 [0, 2, 0]
 [1, 1, 0]
 [2, 0, 0]

Pick the states you need

To compute a transition amplitude you select an initial and a final pattern. findfirst on the occupation numbers is the convenient way:

julia> initial = basis[findfirst(gt -> occupation_number(gt) == [2, 0, 0], basis)];
julia> final = basis[findfirst(gt -> occupation_number(gt) == [1, 1, 0], basis)];

Hand them to a group function

The states plug straight into group_function. Here U is a 50:50 beam splitter on modes 1–2 (built from an SU(2) block); the result is the amplitude $\langle 1,1,0 \mid U \mid 2,0,0\rangle$:

julia> θ = float(π) / 2;
julia> U = su2_block(3, 1, (0., θ, 0.));
julia> group_function(λ, final, initial, U)0.7071067811865476 + 0.0im

That is the whole pipeline: build patterns, pick two, evaluate. What the number means, and the symbolic and multi-block cases, are covered next in group functions.

Mixed symmetry

Everything above used the symmetric irrep, where each pattern has a distinct p-weight. That stops being true for mixed-symmetry irreps. Take λ = [2, 1, 0]:

julia> basis21 = basis_states([2, 1, 0]);
julia> occupation_number.(basis21)8-element Vector{Vector{Int64}}: [0, 1, 2] [1, 0, 2] [1, 1, 1] [0, 2, 1] [1, 1, 1] [2, 0, 1] [1, 2, 0] [2, 1, 0]

Two different patterns share the occupation vector [1, 1, 1]; it is an inner multiplicity. The weight alone no longer identifies the state, and you have to use the full pattern for unique determination. This is exactly why the basis is GT patterns and not occupation numbers, and it is where permanents and determinants give way to the general Grabmeier-Kerber formula.

Next: group functions.