Quantum Objects

using PiccoloQuantumObjects
using SparseArrays # for visualization
⊗ = kron;

Quantum states and operators are represented as complex vectors and matrices. We provide a number of convenient ways to construct these objects. We also provide some tools for working with these objects, such as embedding operators in larger Hilbert spaces and selecting subspace indices.

Quantum states

We can construct quantum states from bitstrings or string representations. The string representations use atomic notation (ground state g, excited state e, etc.).

Ground state in a 2-level system.

ket_from_string("g", [2])
2-element Vector{ComplexF64}:
 1.0 + 0.0im
 0.0 + 0.0im

Superposition state coupled to a ground state in two 2-level systems.

ket_from_string("(g+e)g", [2,2])
4-element Vector{ComplexF64}:
 0.7071067811865475 + 0.0im
                0.0 + 0.0im
 0.7071067811865475 + 0.0im
                0.0 + 0.0im

|01⟩ in a 2-qubit system.

ket_from_bitstring("01")
4-element Vector{ComplexF64}:
 0.0 + 0.0im
 1.0 + 0.0im
 0.0 + 0.0im
 0.0 + 0.0im

Quantum operators

Frequently used operators are provided in PAULIS and GATES.

PiccoloQuantumObjects.Gates.GATESConstant

A constant dictionary GATES containing common quantum gate matrices as complex-valued matrices. Each gate is represented by its unitary matrix.

  • GATES[:I] - Identity: Leaves the state unchanged.
  • GATES[:X] - Pauli-X (NOT): Flips the qubit state.
  • GATES[:Y] - Pauli-Y: Rotates the qubit state around the Y-axis of the Bloch sphere.
  • GATES[:Z] - Pauli-Z: Flips the phase of the qubit state.
  • GATES[:H] - Hadamard: Creates superposition by transforming basis states.
  • GATES[:CX] - Controlled-X (CNOT): Flips the 2nd qubit (target) if the first qubit (control) is |1⟩.
  • GATES[:CZ] - Controlled-Z (CZ): Flips the phase of the 2nd qubit (target) if the 1st qubit (control) is |1⟩.
  • GATES[:XI] - Complex: A gate for complex operations.
  • GATES[:sqrtiSWAP] - Square root of iSWAP: Partially swaps two qubits with a phase.
source

Quantum operators can also be constructed from strings.

operator_from_string("X")
2×2 Matrix{ComplexF64}:
 0.0+0.0im  1.0+0.0im
 1.0+0.0im  0.0+0.0im
operator_from_string("XZ")
4×4 Matrix{ComplexF64}:
 0.0+0.0im   0.0+0.0im  1.0+0.0im   0.0+0.0im
 0.0+0.0im  -0.0+0.0im  0.0+0.0im  -1.0+0.0im
 1.0+0.0im   0.0+0.0im  0.0+0.0im   0.0+0.0im
 0.0+0.0im  -1.0+0.0im  0.0+0.0im  -0.0+0.0im

Annihilation and creation operators are provided for oscillator systems.

a = annihilate(3)
3×3 Matrix{ComplexF64}:
 0.0+0.0im  1.0+0.0im      0.0+0.0im
 0.0+0.0im  0.0+0.0im  1.41421+0.0im
 0.0+0.0im  0.0+0.0im      0.0+0.0im
a⁺ = create(3)
3×3 Matrix{ComplexF64}:
 0.0-0.0im      0.0-0.0im  0.0-0.0im
 1.0-0.0im      0.0-0.0im  0.0-0.0im
 0.0-0.0im  1.41421-0.0im  0.0-0.0im
a'a
3×3 Matrix{ComplexF64}:
 0.0+0.0im  0.0+0.0im  0.0+0.0im
 0.0+0.0im  1.0+0.0im  0.0+0.0im
 0.0+0.0im  0.0+0.0im  2.0+0.0im

Random operators

The haar_random function draws random unitary operators according to the Haar measure.

haar_random(3)
3×3 Matrix{ComplexF64}:
 0.184163+0.545824im   -0.0719749-0.336064im    0.346066-0.655957im
 0.447938+0.0550865im    0.183148-0.723243im  -0.0714509+0.484343im
  0.42332-0.534114im     0.550934+0.147407im    0.375195-0.263618im

If we want to generate random operations that are close to the identity, we can use the haar_identity function.

haar_identity(2, 0.1)
2×2 Matrix{ComplexF64}:
   0.99021+0.123495im   -0.00848026-0.0645049im
 0.0133564-0.0636742im     0.996731-0.0479083im

A smaller radius means the random operator is closer to the identity.

haar_identity(2, 0.01)
2×2 Matrix{ComplexF64}:
   0.999871+0.0135093im   -0.0059982+0.00624879im
 0.00583716+0.00639948im    0.999891+0.0119509im

Embedded operators

Sometimes we want to embed a quantum operator into a larger Hilbert space, $\mathcal{H}$, which we decompose into subspace and leakage components:

\[ \mathcal{H} = \mathcal{H}_{\text{subspace}} \oplus \mathcal{H}_{\text{leakage}},\]

In quantum computing, the computation is encoded in a subspace, while the remaining leakage states should be avoided.

The embed and unembed functions

The embed function allows to embed a quantum operator in a larger Hilbert space.

PiccoloQuantumObjects.EmbeddedOperators.embedFunction
embed(operator::AbstractMatrix{<:Number}, subspace::AbstractVector{Int}, levels::Int)

Embed an operator in the subspace of a larger matrix of size levels x levels.

source
embed(subspace_operator::AbstractMatrix{<:Number}, embedded_operator::EmbeddedOperator)

Embed the subspace_operator in the subspace of a larger embedded_operator.

source

The unembed function allows to unembed a quantum operator from a larger Hilbert space.

PiccoloQuantumObjects.EmbeddedOperators.unembedFunction
unembed(matrix::AbstractMatrix{<:Number}, subspace::AbstractVector{Int})

Unembed a subspace operator from the matrix. This is equivalent to calling matrix[subspace, subspace].

source
unembed(embedded_op::EmbeddedOperator)::Matrix{ComplexF64}

Unembed an embedded operator, returning the original operator.

source
unembed(op::AbstractMatrix, embedded_op::EmbeddedOperator)

Unembed a sub-matrix from the op at the subspace defined by embedded_op.

source

Embed a two-level X gate into a multilevel system.

levels = 3
X = GATES[:X]
subspace_indices = 1:2
X_embedded = embed(X, subspace_indices, levels)
3×3 Matrix{ComplexF64}:
 0.0+0.0im  1.0+0.0im  0.0+0.0im
 1.0+0.0im  0.0+0.0im  0.0+0.0im
 0.0+0.0im  0.0+0.0im  0.0+0.0im

Unembed to retrieve the original operator.

X_original = unembed(X_embedded, subspace_indices)
2×2 Matrix{ComplexF64}:
 0.0+0.0im  1.0+0.0im
 1.0+0.0im  0.0+0.0im

The EmbeddedOperator type

The EmbeddedOperator type stores information about an operator embedded in the subspace of a larger quantum system.

PiccoloQuantumObjects.EmbeddedOperators.EmbeddedOperatorType
EmbeddedOperator

Embedded operator type to represent an operator embedded in a subspace of a larger quantum system.

Fields

  • operator::Matrix{ComplexF64}: Embedded operator of size prod(subsystem_levels) x prod(subsystem_levels).
  • subspace::Vector{Int}: Indices of the subspace the operator is embedded in.
  • subsystem_levels::Vector{Int}: Levels of the subsystems in the composite system.
source

We construct an embedded operator in the same manner as the embed function.

PiccoloQuantumObjects.EmbeddedOperators.EmbeddedOperatorMethod
EmbeddedOperator(subspace_operator::Matrix{<:Number}, subspace::AbstractVector{Int}, subsystem_levels::AbstractVector{Int})

Create an embedded operator. The operator is embedded at the subspace of the system spanned by the subsystem_levels.

source

Embed an X gate in the first qubit's subspace within two 3-level systems.

gate = GATES[:X] ⊗ GATES[:I]
subsystem_levels = [3, 3]
subspace_indices = get_subspace_indices([1:2, 1:2], subsystem_levels)
embedded_operator = EmbeddedOperator(gate, subspace_indices, subsystem_levels)
EmbeddedOperator(ComplexF64[0.0 + 0.0im 0.0 + 0.0im … 0.0 + 0.0im 0.0 + 0.0im; 0.0 + 0.0im 0.0 + 0.0im … 0.0 + 0.0im 0.0 + 0.0im; … ; 0.0 + 0.0im 0.0 + 0.0im … 0.0 + 0.0im 0.0 + 0.0im; 0.0 + 0.0im 0.0 + 0.0im … 0.0 + 0.0im 0.0 + 0.0im], [1, 2, 4, 5], [3, 3])

Show the full operator.

embedded_operator.operator .|> real |> sparse
9×9 SparseArrays.SparseMatrixCSC{Float64, Int64} with 4 stored entries:
  ⋅    ⋅    ⋅   1.0   ⋅    ⋅    ⋅    ⋅    ⋅ 
  ⋅    ⋅    ⋅    ⋅   1.0   ⋅    ⋅    ⋅    ⋅ 
  ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅ 
 1.0   ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅ 
  ⋅   1.0   ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅ 
  ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅ 
  ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅ 
  ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅ 
  ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅    ⋅ 

Get the original operator back.

unembed(embedded_operator) .|> real |> sparse
4×4 SparseArrays.SparseMatrixCSC{Float64, Int64} with 4 stored entries:
  ⋅    ⋅   1.0   ⋅ 
  ⋅    ⋅    ⋅   1.0
 1.0   ⋅    ⋅    ⋅ 
  ⋅   1.0   ⋅    ⋅ 

Embedded operators for composite systems are also supported.

PiccoloQuantumObjects.EmbeddedOperators.EmbeddedOperatorMethod
EmbeddedOperator(
    subspace_operator::AbstractMatrix{<:Number},
    subsystem_indices::AbstractVector{Int},
    subspaces::AbstractVector{<:AbstractVector{Int}},
    subsystem_levels::AbstractVector{Int}
)

Embed the subspace_operator into the provided subspaces of a composite system, where the subsystem_indices list the subspaces at which the operator is defined, and the subsystem_levels list the levels of the subsystems in which the operator is embedded.

source

This is a two step process.

  1. The provided subspace operator is lift-ed from the subsystem_indices where it is defined into the space spanned by the composite system's subspaces.
  2. The lifted operator is embedded into the full Hilbert space spanned by the subsystem_levels.

Embed a CZ gate with control qubit 1 and target qubit 3 into a composite system made up of three 3-level systems. An identity is performed on qubit 2.

subsystem_levels = [3, 3, 3]
subspaces = [1:2, 1:2, 1:2]
embedded_operator = EmbeddedOperator(GATES[:CZ], [1, 3], subspaces, subsystem_levels)
unembed(embedded_operator) .|> real |> sparse
8×8 SparseArrays.SparseMatrixCSC{Float64, Int64} with 8 stored entries:
 1.0   ⋅    ⋅    ⋅    ⋅     ⋅    ⋅     ⋅ 
  ⋅   1.0   ⋅    ⋅    ⋅     ⋅    ⋅     ⋅ 
  ⋅    ⋅   1.0   ⋅    ⋅     ⋅    ⋅     ⋅ 
  ⋅    ⋅    ⋅   1.0   ⋅     ⋅    ⋅     ⋅ 
  ⋅    ⋅    ⋅    ⋅   1.0    ⋅    ⋅     ⋅ 
  ⋅    ⋅    ⋅    ⋅    ⋅   -1.0   ⋅     ⋅ 
  ⋅    ⋅    ⋅    ⋅    ⋅     ⋅   1.0    ⋅ 
  ⋅    ⋅    ⋅    ⋅    ⋅     ⋅    ⋅   -1.0

Subspace and leakage indices

The get_subspace_indices function

The get_subspace_indices function is a convenient way to get the indices of a subspace in a larger quantum system.

PiccoloQuantumObjects.EmbeddedOperators.get_subspace_indicesFunction
get_subspace_indices(subspace::AbstractVector{Int}, levels::Int)
get_subspace_indices(subspaces::Vector{<:AbstractVector{Int}}, subsystem_levels::AbstractVector{Int})
get_subspace_indices(subsystem_levels::AbstractVector{Int}; subspace=1:2)
get_subspace_indices(op::EmbeddedOperator)

Get the indices for the provided subspace of the quantum system.

source

Its dual function is get_leakage_indices.

get_subspace_indices(1:2, 5) |> collect, get_leakage_indices(1:2, 5) |> collect
([1, 2], [3, 4, 5])

Composite systems are supported. Get the indices of the two-qubit subspace within two 3-level systems.

get_subspace_indices([1:2, 1:2], [3, 3])
4-element Vector{Int64}:
 1
 2
 4
 5

Qubits are assumed if the indices are not provided.

get_subspace_indices([3, 3])
4-element Vector{Int64}:
 1
 2
 4
 5
get_leakage_indices([3, 3])
5-element Vector{Int64}:
 3
 6
 7
 8
 9

Excitation number restrictions

Sometimes we want to cap the number of excitations we allow across a composite system. For example, if we want to restrict ourselves to the ground and single excitation states of two 3-level systems:

get_enr_subspace_indices(1, [3, 3])
3-element Vector{Int64}:
 1
 2
 4

The get_iso_vec_subspace_indices function

For isomorphic operators, the get_iso_vec_subspace_indices function can be used to find the appropriate vector indices of the equivalent operator subspace. See also, Isomorphisms#Quantum-operator-isomorphisms.

Its dual function is get_iso_vec_leakage_indices, which by default only returns the leakage indices of the blocks:

\[\mathcal{H}_{\text{subspace}} \otimes \mathcal{H}_{\text{subspace}},\quad \mathcal{H}_{\text{subspace}} \otimes \mathcal{H}_{\text{leakage}},\quad \mathcal{H}_{\text{leakage}} \otimes \mathcal{H}_{\text{subspace}}\]

allowing for leakage-suppressing code to disregard the uncoupled pure-leakage space.

get_iso_vec_subspace_indices(1:2, 3)
8-element Vector{Int64}:
  1
  2
  4
  5
  7
  8
 10
 11
without_pure_leakage = get_iso_vec_leakage_indices(1:2, 3)
8-element Vector{Int64}:
  3
  6
  9
 12
 13
 14
 16
 17

Show the pure-leakage indices.

with_pure_leakage = get_iso_vec_leakage_indices(1:2, 3, ignore_pure_leakage=false)
setdiff(with_pure_leakage, without_pure_leakage)
2-element Vector{Int64}:
 15
 18

The pure-leakage indices can grow quickly!

without_pure_leakage = get_iso_vec_leakage_indices([1:2, 1:2], [4, 4])
with_pure_leakage = get_iso_vec_leakage_indices([1:2, 1:2], [4, 4], ignore_pure_leakage=false)
setdiff(with_pure_leakage, without_pure_leakage) |> length
288

This page was generated using Literate.jl.