Hilbert spaces and operators

A model starts from the Hilbert space of each subsystem. The type of the space fixes which operators live on it and which algebraic relations they obey:

SubsystemSpaceOperatorsDefining relation
Bosonic modeFockSpaceDestroy, Create$[a, a^\dagger] = 1$
Finite-level systemNLevelSpaceTransition$\sigma^{ij}\sigma^{kl} = \delta_{jk}\sigma^{il}$
Qubit in Pauli formPauliSpacePauli$\sigma_j\sigma_k = \delta_{jk} + i\epsilon_{jkl}\sigma_l$
Angular momentumSpinSpaceSpin$[S_j, S_k] = i\epsilon_{jkl}S_l$
Canonical quadraturesPhaseSpacePosition, Momentum$[x, p] = i$

The examples below illustrate these spaces with a cavity mode, a two-level atom, a spin, and the position and momentum of a mechanical mirror. We combine them in one product space to show how operators address individual subsystems. Choose the spaces needed for your own model; no coupling between subsystems is implied by the product space.

Hilbert spaces

Each subsystem gets its own space, labelled by a name. The atom lists its levels by name, which then serve to address its transitions.

using SecondQuantizedAlgebrahc = FockSpace(:cavity)ha = NLevelSpace(:atom, (:g, :e))hs = SpinSpace(:spin)hm = PhaseSpace(:mirror)hc, ha, hs, hm
(ℋ(cavity), ℋ(atom), ℋ(spin), ℋ(mirror))

All of them share the supertype HilbertSpace.

SecondQuantizedAlgebra.NLevelSpace — Type
NLevelSpace(name::Symbol, n::Int)
NLevelSpace(name::Symbol, n::Int, ground_state::Int)
NLevelSpace(name::Symbol, levels::Tuple{Vararg{Symbol}})

Hilbert space of a system with n discrete levels, home of the Transition operators.

Levels are numbered 1:n, or named by a tuple of symbols in the order of the basis. The ground_state (default 1; the first name for named levels) marks the projector $|g\rangle\langle g|$ that expand_completeness can eliminate through $|g\rangle\langle g| = 1 - \sum_{k \neq g}|k\rangle\langle k|$.

Examples

julia> h = NLevelSpace(:atom, (:g, :e))ℋ(atom)julia> σ = Transition(h, :σ, :g, :e);julia> σ' * σσ₂₂julia> h3 = NLevelSpace(:atom, 3, 2);julia> expand_completeness(Transition(h3, :σ, 2, 2))1 - σ₁₁ - σ₃₃

See also Transition, expand_completeness, PauliSpace.

source
SecondQuantizedAlgebra.SpinSpace — Type
SpinSpace(name::Symbol) <: HilbertSpace

Hilbert space of an angular momentum, home of the Spin operators.

The spin length $S$ does not enter the algebra. It is fixed only by the basis chosen for numerics, so the same expressions hold for every $S$.

Examples

julia> h = SpinSpace(:S)ℋ(S)julia> Sz = Spin(h, :S, 3)Sz

See also Spin, PauliSpace.

source
SecondQuantizedAlgebra.PhaseSpace — Type
PhaseSpace(name::Symbol) <: HilbertSpace

Hilbert space of one mode in quadrature form, home of Position and Momentum.

A PhaseSpace holds a single canonical pair. The quadrature names are only labels: a second Position under another name is the same observable. Several modes therefore need several spaces combined with ⊗.

Examples

julia> h = PhaseSpace(:osc)ℋ(osc)julia> x = Position(h, :x); y = Position(h, :y); p = Momentum(h, :p);julia> commutator(x, p), commutator(y, p)(im, im)

See also Position, Momentum, FockSpace.

source

Composite systems

Subsystems are joined with the tensor product. Its factors are numbered from left to right, and this number is how an operator refers to the subsystem it acts on: here the cavity is factor 1 and the mirror factor 4.

h = hc ⊗ ha ⊗ hs ⊗ hm
ℋ(cavity) ⊗ ℋ(atom) ⊗ ℋ(spin) ⊗ ℋ(mirror)
TensorCore.:⊗ — Method
⊗(space::HilbertSpace, spaces::HilbertSpace...)

Tensor product of Hilbert spaces, returning a ProductSpace.

Nested products are flattened, so (A ⊗ B) ⊗ C == A ⊗ (B ⊗ C) and the factors are always numbered left to right. Type it as \otimes<tab>, or use the ASCII alias tensor.

Examples

julia> A = FockSpace(:a); B = FockSpace(:b); C = NLevelSpace(:atom, 2);julia> A ⊗ B ⊗ Cℋ(a) ⊗ ℋ(b) ⊗ ℋ(atom)julia> (A ⊗ B) ⊗ C == A ⊗ (B ⊗ C)true

See also ProductSpace, tensor.

source
SecondQuantizedAlgebra.ProductSpace — Type
ProductSpace{T} <: HilbertSpace

Tensor product of Hilbert spaces, built with ⊗ or tensor.

The factors are numbered 1, 2, … from left to right. An operator on a product space names the factor it acts on by this number, e.g. Destroy(h, :a, 1). The number may be left out when only one factor has the right type; otherwise the constructor throws an ArgumentError.

Examples

julia> h = FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2)ℋ(cavity) ⊗ ℋ(atom)julia> a = Destroy(h, :a, 1); σ = Transition(h, :σ, 1, 2);julia> a * σa * σ₁₂

See also ⊗, acts_on.

source

Operators

An operator is built from its space, a name, and whatever else fixes it: two levels for a transition, an axis for Pauli and spin operators. The name belongs to the identity of the operator, so two modes with different names are independent. Writing every name twice, once for the variable and once for the operator, is avoided with @qnumbers.

@qnumbers a::Destroy(h, 1) x::Position(h, 4) p::Momentum(h, 4)a, x, p
(a, x, p)
SecondQuantizedAlgebra.@qnumbers — Macro
@qnumbers ops...

Define operators and bind each to a variable of the same name.

Each argument reads name::Constructor(space, args...) and stands for name = Constructor(space, :name, args...), so the operator name is written once. Several operators can be declared in one call.

Examples

julia> h = FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2);julia> @qnumbers a::Destroy(h, 1) σ::Transition(h, 1, 2, 2)(a, σ₁₂)julia> a' * σa' * σ₁₂

See also Destroy, Transition, Pauli, Spin, Position, Momentum, CollectiveTransition.

source

Bosonic modes

A mode is described by its annihilation operator. The creation operator follows as its adjoint, so it rarely needs to be constructed on its own.

a' == Create(h, :a, 1)
true
SecondQuantizedAlgebra.Destroy — Function
Destroy(h::FockSpace, name::Symbol)
Destroy(h::ProductSpace, name::Symbol, [subsystem::Int])

Annihilation operator of the bosonic mode name.

The operator $a$ obeys $[a, a^\dagger] = 1$, and products come out normal ordered. The adjoint a' is the Create operator. Modes with different names are independent. On a ProductSpace, subsystem selects the FockSpace factor and may be omitted when there is only one.

Examples

julia> h = FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2);julia> a = Destroy(h, :a);julia> a * a'1 + a' * a

See also Create, FockSpace, @qnumbers.

source

Finite-level systems

Atoms, qudits and other discrete systems are written in terms of transitions between their levels, addressed by the level names. Projectors are the special case of equal levels.

σ(i, j) = Transition(h, :σ, i, j, 2)σ(:g, :e), σ(:e, :e)
(σ₁₂, σ₂₂)
SecondQuantizedAlgebra.Transition — Function
Transition(h::NLevelSpace, name::Symbol, i, j)
Transition(h::ProductSpace, name::Symbol, i, j, [subsystem::Int])

Transition operator from level j to level i.

The operator is $\sigma^{ij} = |i\rangle\langle j|$. The levels are integers or the symbolic names of the NLevelSpace; either way it prints with level numbers. Transitions on the same system compose, $\sigma^{ij}\sigma^{kl} = \delta_{jk}\sigma^{il}$, and the adjoint swaps the levels. The ground-state projector $\sigma^{gg}$ is kept as it is; call expand_completeness to rewrite it in terms of the other projectors. On a ProductSpace, subsystem selects the factor and may be omitted when only one NLevelSpace is present.

Examples

julia> h = FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2);julia> σ = Transition(h, :σ, 1, 2);julia> σ * σ', σ' * σ, σ * σ(σ₁₁, σ₂₂, 0)

See also NLevelSpace, expand_completeness, @qnumbers.

source

A transition stores its levels as numbers, in the order the space names them, together with the level count and the ground level.

using SecondQuantizedAlgebra: n_levels, ground_state, transition_levelstransition_levels(σ(:g, :e)), n_levels(σ(:g, :e)), ground_state(σ(:g, :e))
((1, 2), 2, 1)

Pauli and spin operators

A two-level system can equally be written with Pauli matrices, which reduce every product to a single matrix or a number.

τ(k) = Pauli(PauliSpace(:qubit), :τ, k)τ(1) * τ(1), τ(1) * τ(2)
(1, im * τz)

Spin operators extend this to angular momenta of unspecified size, such as the collective spin of the model.

S(k) = Spin(h, :S, k, 3)S(1), S(2), S(3)
(Sx, Sy, Sz)
SecondQuantizedAlgebra.Pauli — Function
Pauli(h::PauliSpace, name::Symbol, axis::Int)
Pauli(h::ProductSpace, name::Symbol, axis::Int, [subsystem::Int])

Pauli matrix along axis, where 1, 2, 3 stand for x, y, z.

Pauli operators are Hermitian and multiply as $\sigma_j \sigma_k = \delta_{jk} + i \epsilon_{jkl} \sigma_l$, so every product on one qubit reduces to a single Pauli matrix or a number. On a ProductSpace, subsystem selects the PauliSpace factor and may be omitted when there is only one.

Examples

julia> h = PauliSpace(:s);julia> σx = Pauli(h, :σ, 1); σy = Pauli(h, :σ, 2);julia> σx * σyim * σzjulia> σx * σx1

See also PauliSpace, Spin.

source
SecondQuantizedAlgebra.Spin — Function
Spin(h::SpinSpace, name::Symbol, axis::Int)
Spin(h::ProductSpace, name::Symbol, axis::Int, [subsystem::Int])

Angular momentum component along axis, where 1, 2, 3 stand for x, y, z.

Spin operators are Hermitian and obey $[S_j, S_k] = i \epsilon_{jkl} S_l$. Products are ordered by axis, $x$ before $y$ before $z$. Squares such as $S_x^2$ stay as they are, since the spin length is not fixed. On a ProductSpace, subsystem selects the SpinSpace factor and may be omitted when there is only one.

Examples

julia> h = SpinSpace(:S);julia> Sx = Spin(h, :S, 1); Sy = Spin(h, :S, 2);julia> Sy * Sx-im * Sz + Sx * Sy

See also SpinSpace, Pauli.

source

The axis of a Pauli or spin operator is read back as 1, 2 or 3.

using SecondQuantizedAlgebra: operator_axisoperator_axis(τ(2)), operator_axis(S(3))
(2, 3)

Quadratures

A mode can also be described by its position and momentum quadratures instead of ladder operators, as the mirror is here.

p * x

\[-\mathit{i} + \hat{x}\hat{p}\]

SecondQuantizedAlgebra.Position — Function
Position(h::PhaseSpace, name::Symbol)
Position(h::ProductSpace, name::Symbol, [subsystem::Int])

Position quadrature of a mode.

The operator $x = (a + a^\dagger)/\sqrt{2}$ is Hermitian and canonically conjugate to Momentum, $[x, p] = i$. Products are ordered with position to the left of momentum. On a ProductSpace, subsystem selects the PhaseSpace factor and may be omitted when there is only one.

Examples

julia> h = PhaseSpace(:osc);julia> @qnumbers x::Position(h) p::Momentum(h);julia> commutator(x, p)im

See also Momentum, PhaseSpace.

source

Inspecting operators

Every operator has the same type, so its kind is read with a function rather than a type check. Methods for any operator expression are written for QField, and for single operators for QSym.

optype(a'), is_create(a'), is_transition(σ(:g, :e))
(SecondQuantizedAlgebra.OP_CREATE, true, true)
SecondQuantizedAlgebra.QField — Type
QField

Supertype of every operator expression.

Operators (QSym), their polynomials (QAdd) and expressions with formal functions (QExpr) are all QFields. They support +, -, *, / by numbers, integer powers, adjoint, and comparison with == and isequal. A method written for QField accepts any of them.

Examples

julia> a = Destroy(FockSpace(:cavity), :a);julia> (a, a' * a, cos(a + a')) isa NTuple{3, SecondQuantizedAlgebra.QField}true

See also QSym, QAdd, QExpr.

source
SecondQuantizedAlgebra.Op — Type
Op <: QSym

Type of every single operator, whatever its kind.

Operators are made with Destroy, Create, Transition, CollectiveTransition, Pauli, Spin, Position and Momentum, never with Op directly. Since all of them share this type, use optype or the predicates such as is_destroy to tell them apart instead of isa.

Examples

julia> a = Destroy(FockSpace(:cavity), :a);julia> a isa Op, is_destroy(a), is_create(a')(true, true, true)

See also optype, operator_name, acts_on.

source
SecondQuantizedAlgebra.optype — Function
optype(o::Op) -> OpKind

Kind of the operator o.

The result is one of OP_DESTROY, OP_CREATE, OP_TRANSITION, OP_COLLECTIVE_TRANSITION, OP_PAULI, OP_SPIN, OP_POSITION or OP_MOMENTUM. These constants are public but not exported, so write them qualified, e.g. SecondQuantizedAlgebra.OP_DESTROY. For a single check the predicates such as is_destroy read better.

Examples

julia> σ = Transition(NLevelSpace(:atom, 2), :σ, 2, 1);julia> optype(σ) == SecondQuantizedAlgebra.OP_TRANSITIONtrue

See also Op, is_destroy.

source

The name, the site index and the subsystems of an operator or an expression are read off directly. The coupling between the cavity and the atom acts on factors 1 and 2.

operator_name(σ(:g, :e)), acts_on(a' * σ(:g, :e))
(:σ, [1, 2])
SecondQuantizedAlgebra.acts_on — Function
acts_on(expr) -> Vector{Int}
acts_on(i::Index) -> Vector{Int}

Subsystems that expr acts on, as sorted positions in its ProductSpace.

Works on operators, operator expressions and their averages. A number acts on nothing and returns an empty vector. For an Index it returns the subsystem the index runs over.

Examples

julia> h = FockSpace(:a) ⊗ NLevelSpace(:b, 2);julia> @qnumbers a::Destroy(h, 1) σ::Transition(h, 1, 2, 2);julia> acts_on(a' * a), acts_on(a' * σ)([1], [1, 2])

See also operator_name, operator_index.

source
SecondQuantizedAlgebra.set_acts_on — Function
set_acts_on(op, subsystem::Int)

Copy of the operator op acting on another factor of a product space.

Name, kind, levels and index are kept. This reuses an operator built on a single space inside a product space, or moves it to another factor. The function is not exported.

Examples

julia> using SecondQuantizedAlgebra: set_acts_onjulia> σ = Transition(NLevelSpace(:atom, 2), :σ, 2, 1);julia> h = FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2);julia> Destroy(h, :a, 1) * set_acts_on(σ, 2)a * σ₂₁

See also acts_on, rename.

source

Adjoints

The adjoint ' of an expression conjugates its coefficients along with the operators. dagger is the same under the name used by QuantumOptics.jl.

@variables g::Numberdagger(g * a' * σ(:g, :e))

\[\mathrm{conj}\left( g \right) a{\sigma}^{{21}}\]

qadjoint applies to coefficients as well. Unlike conj, it takes the conjugate through to the individual parameters.

@variables θqadjoint(g^2 * cos(θ)), conj(g^2 * cos(θ))
((conj(g)^2)*cos(θ), conj(cos(θ)*(g^2)))
QuantumInterface.dagger — Method
dagger(x::QField)

Hermitian conjugate of an operator expression, the same as x'.

Coefficients are conjugated along with the operators. The name matches dagger from QuantumOptics.jl, so code can use it for symbolic and numeric operators alike.

Examples

julia> h = FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2);julia> @qnumbers a::Destroy(h, 1) σ::Transition(h, 1, 2, 2);julia> dagger(2im * a' * σ)-2im * a * σ₂₁

See also qadjoint.

source
SecondQuantizedAlgebra.qadjoint — Function
qadjoint(x)
qconj(x)

Hermitian conjugate of an operator expression or a coefficient.

For an operator expression this is x'. For a coefficient the conjugate is taken through the expression tree, so that it reaches the individual parameters instead of wrapping the whole expression in conj. An average becomes the average of the adjoint, as with inner_adjoint.

Examples

julia> a = Destroy(FockSpace(:cavity), :a);julia> @variables g::Number;julia> qadjoint(2im * a), qadjoint(g)(-2im * a', conj(g))

See also dagger, inner_adjoint.

source

Printing and LaTeX

Expressions print with Unicode subscripts in the REPL. With Latexify.jl loaded, latexify turns any expression into LaTeX.

using SecondQuantizedAlgebra, Latexifyh = FockSpace(:cavity) ⊗ NLevelSpace(:atom, 2)@qnumbers a::Destroy(h, 1) σ::Transition(h, 2, 1, 2)@variables glatexify(g * (a' * σ + σ' * a))

\[g a{\sigma}^{{12}} + g a^{\dagger}{\sigma}^{{21}}\]

SecondQuantizedAlgebra.transition_superscript — Function
transition_superscript(x::Bool)

Choose whether transition levels are written as superscripts or subscripts in LaTeX.

With true, the default, $\sigma^{ij}$ is written; with false, $\sigma_{ij}$. The setting is global and applies to every later call of latexify. The function is not exported.

Examples

julia> using SecondQuantizedAlgebra: transition_superscriptjulia> using Latexifyjulia> σ = Transition(NLevelSpace(:atom, 2), :σ, 2, 1);julia> transition_superscript(false); latexify(σ)L"${\sigma}_{{21}}$"julia> transition_superscript(true); latexify(σ)L"${\sigma}^{{21}}$"

See also Transition.

source