Quantum Objects (Qobj)
Introduction
The key difference between classical and quantum mechanics is the use of operators instead of numbers as variables. Moreover, we need to specify state vectors and their properties. Therefore, in computing the dynamics of quantum systems, we need a data structure that encapsulates the properties of a quantum operator and ket/bra vectors. The quantum object structure, QuantumObject
, accomplishes this using array representation.
QuantumObject
supports general Julia
arrays (Base.AbstractArray
). For example, it can be:
Base.Vector
(dense vector)Base.Matrix
(dense matrix)SparseArrays.SparseVector
(sparse vector)SparseArrays.SparseMatrixCSC
(sparse matrix)CUDA.CuArray
(dense GPU vector / matrix)CUDA.CUSPARSE.CuSparseVector
(sparse GPU vector)CUDA.CUSPARSE.CuSparseMatrixCSC
(sparse GPU matrix)CUDA.CUSPARSE.CuSparseMatrixCSR
(sparse GPU matrix)and even more ...
Support for GPU arrays
See CUDA extension for more details.
We can create a QuantumObject
with a user defined data set by passing an array of data into the QuantumObject
:
QuantumObject([1, 2, 3, 4, 5])
Quantum Object: type=Ket dims=[5] size=(5,)
5-element Vector{Int64}:
1
2
3
4
5
We can also use the same function Qobj
in QuTiP
(Python
):
Qobj([1, 2, 3, 4, 5])
Quantum Object: type=Ket dims=[5] size=(5,)
5-element Vector{Int64}:
1
2
3
4
5
Qobj([1 2 3 4 5])
Quantum Object: type=Bra dims=[5] size=(1, 5)
1×5 Matrix{Int64}:
1 2 3 4 5
Qobj(rand(4, 4))
Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=false
4×4 Matrix{Float64}:
0.436136 0.0523541 0.125534 0.523377
0.333953 0.937047 0.715633 0.359658
0.57749 0.968143 0.245974 0.199163
0.116653 0.201873 0.0421517 0.45364
M = rand(ComplexF64, 4, 4)
Qobj(M, dims = [2, 2]) # dims as Vector
Qobj(M, dims = (2, 2)) # dims as Tuple (recommended)
Qobj(M, dims = SVector(2, 2)) # dims as StaticArrays.SVector (recommended)
Quantum Object: type=Operator dims=[2, 2] size=(4, 4) ishermitian=false
4×4 Matrix{ComplexF64}:
0.242822+0.595937im 0.845335+0.815778im … 0.204792+0.885239im
0.46183+0.397738im 0.038032+0.207129im 0.167271+0.469069im
0.639868+0.00135075im 0.370842+0.352966im 0.271874+0.473101im
0.940338+0.710936im 0.900482+0.765282im 0.804241+0.657217im
Beware of type-stability!
Please note that here we put the dims
as a tuple (2, 2)
. Although it supports also Vector
type (dims = [2, 2]
), it is recommended to use Tuple
or SVector
from StaticArrays.jl
to improve performance. For a brief explanation on the impact of the type of dims
, see the Section The Importance of Type-Stability.
Qobj(rand(4, 4), type = SuperOperator)
Quantum Object: type=SuperOperator dims=[2] size=(4, 4)
4×4 Matrix{Float64}:
0.410389 0.721126 0.398799 0.487883
0.643279 0.71733 0.0409458 0.399743
0.222834 0.345121 0.6159 0.969404
0.255762 0.188746 0.350295 0.548891
Difference between dims
and size
Notice that type
, dims
, and size
will change according to the input data
. Although dims
and size
appear to be the same, dims
keep tracking the dimension of individual Hilbert spaces of a multipartite system, while size
does not. We refer the reader to the section Tensor Products and Partial Traces for more information.
States and operators
Manually specifying the data for each quantum object is inefficient. Even more so when most objects correspond to commonly used types such as the ladder operators of a harmonic oscillator, the Pauli spin operators for a two-level system, or state vectors such as Fock states. Therefore, QuantumToolbox
includes predefined functions to construct variety of states and operators (you can click the function links and see the corresponding docstring):
States
zero_ket
: zero ket vectorfock_dm
: density matrix of a fock statecoherent
: coherent state ket vectorrand_ket
: random ket vectorcoherent_dm
: density matrix of a coherent statethermal_dm
: density matrix of a thermal statemaximally_mixed_dm
: density matrix of a maximally mixed staterand_dm
: random density matrixspin_state
: spin statespin_coherent
: coherent spin statebell_state
: Bell statesinglet_state
: two particle singlet statetriplet_states
: list of the two particle triplet statesw_state
:n
-qubit W-stateghz_state
:n
-qudit GHZ state
Operators
destroy
: lowering (destruction) operatorcreate
: raising (creation) operatorprojection
: projection operatordisplace
: displacement operatorsqueeze
: single-mode squeeze operatornum
: bosonic number operatorphase
: single-mode Pegg-Barnett phase operatorQuantumToolbox.position
: position operatorQuantumToolbox.momentum
: momentum operatorrand_unitary
: random unitary operatorsigmax
: Pauli-operator sigmay
: Pauli-operator sigmaz
: Pauli-operator sigmap
: Pauli ladder () operator sigmam
: Pauli ladder () operator jmat
: high-order Spin-j
operatorspin_Jx
:operator for Spin- j
spin_Jy
:operator for Spin- j
spin_Jz
:operator for Spin- j
spin_Jm
:operator for Spin- j
spin_Jp
:operator for Spin- j
spin_J_set
: a set of Spin-j
operatorsfdestroy
: fermion destruction operatorfcreate
: fermion creation operatorcommutator
: commutator or anti-commutatortunneling
: tunneling operatorqft
: discrete quantum Fourier transform matrix
As an example, we give the output for a few of these functions:
basis(5, 3)
Quantum Object: type=Ket dims=[5] size=(5,)
5-element Vector{ComplexF64}:
0.0 + 0.0im
0.0 + 0.0im
0.0 + 0.0im
1.0 + 0.0im
0.0 + 0.0im
coherent(5, 0.5 - 0.5im)
Quantum Object: type=Ket dims=[5] size=(5,)
5-element Vector{ComplexF64}:
0.7788017020755937 + 0.0im
0.3893914169377068 - 0.3893914169377068im
0.0 - 0.27545895152395im
-0.0789861710068352 - 0.0789861710068352im
-0.04314270829853118 + 0.0im
destroy(4)
Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=false
4×4 SparseMatrixCSC{ComplexF64, Int64} with 3 stored entries:
⋅ 1.0+0.0im ⋅ ⋅
⋅ ⋅ 1.41421+0.0im ⋅
⋅ ⋅ ⋅ 1.73205+0.0im
⋅ ⋅ ⋅ ⋅
sigmaz()
Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true
2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries:
1.0+0.0im ⋅
⋅ -1.0+0.0im
Qobj fields (attributes)
We have seen that a structure QuantumObject
has several fields (attributes), such as data
, type
and dims
. These can be accessed in the following way:
a = destroy(4)
a.data
4×4 SparseMatrixCSC{ComplexF64, Int64} with 3 stored entries:
⋅ 1.0+0.0im ⋅ ⋅
⋅ ⋅ 1.41421+0.0im ⋅
⋅ ⋅ ⋅ 1.73205+0.0im
⋅ ⋅ ⋅ ⋅
a[2, 3] # the indexing in Julia starts from `1`
1.4142135623730951 + 0.0im
a.type
Operator
a.dims
1-element SVector{1, Int64} with indices SOneTo(1):
4
In general, the properties of a QuantumObject
can be retrieved using several functions with inputting QuantumObject
:
size(a)
(4, 4)
shape(a) # synonym of size(a)
(4, 4)
length(a)
16
eltype(a) # element type
ComplexF64 (alias for Complex{Float64})
println(isket(a)) # ket
println(isbra(a)) # bra
println(isoper(a)) # operator
println(isoperket(a)) # operator-ket
println(isoperbra(a)) # operator-bra
println(issuper(a)) # super operator
println(isconstant(a)) # time-independent or not
println(ishermitian(a)) # Hermitian
println(isherm(a)) # synonym of ishermitian(a)
println(issymmetric(a)) # symmetric
println(isposdef(a)) # positive definite (and Hermitian)
println(isunitary(a)) # unitary
false
false
true
false
false
false
true
false
false
false
false
false
data
conversions
As we mentioned above, QuantumObject.data
supports general Julia
arrays. The conversion between different type of QuantumObject.data
is done using the standard type-conversion for arrays in Julia
:
v_d = basis(2, 0)
Quantum Object: type=Ket dims=[2] size=(2,)
2-element Vector{ComplexF64}:
1.0 + 0.0im
0.0 + 0.0im
Vector{Int64}(v_d)
Quantum Object: type=Ket dims=[2] size=(2,)
2-element Vector{Int64}:
1
0
v_s = SparseVector(v_d)
Quantum Object: type=Ket dims=[2] size=(2,)
2-element SparseVector{ComplexF64, Int64} with 1 stored entry:
[1] = 1.0+0.0im
SparseVector{Float64}(v_s)
Quantum Object: type=Ket dims=[2] size=(2,)
2-element SparseVector{Float64, Int64} with 1 stored entry:
[1] = 1.0
x_s = sigmax()
Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true
2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries:
⋅ 1.0+0.0im
1.0+0.0im ⋅
SparseMatrixCSC{Int64}(x_s)
Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true
2×2 SparseMatrixCSC{Int64, Int64} with 2 stored entries:
⋅ 1
1 ⋅
Matrix{Float64}(x_s)
Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true
2×2 Matrix{Float64}:
0.0 1.0
1.0 0.0
To convert between dense and sparse arrays, one can also use to_sparse
and to_dense
:
x_d = to_dense(x_s)
Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true
2×2 Matrix{ComplexF64}:
0.0+0.0im 1.0+0.0im
1.0+0.0im 0.0+0.0im
to_sparse(x_d)
Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true
2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries:
⋅ 1.0+0.0im
1.0+0.0im ⋅
Convert to GPU arrays
See CUDA extension for more details.
QuantumToolbox
will do conversion when needed to keep everything working in any format. However these conversions could slow down computation and it is recommended to keep to one format family where possible.
Qobj math
The rules for mathematical operations on QuantumObject
are similar to the standard scalar, vector, and matrix arithmetic:
a = destroy(4)
Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=false
4×4 SparseMatrixCSC{ComplexF64, Int64} with 3 stored entries:
⋅ 1.0+0.0im ⋅ ⋅
⋅ ⋅ 1.41421+0.0im ⋅
⋅ ⋅ ⋅ 1.73205+0.0im
⋅ ⋅ ⋅ ⋅
a' # synonym of adjoint(a)
Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=false
4×4 Adjoint{ComplexF64, SparseMatrixCSC{ComplexF64, Int64}} with 3 stored entries:
⋅ ⋅ ⋅ ⋅
1.0-0.0im ⋅ ⋅ ⋅
⋅ 1.41421-0.0im ⋅ ⋅
⋅ ⋅ 1.73205-0.0im ⋅
a + 5
Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=false
4×4 SparseMatrixCSC{ComplexF64, Int64} with 7 stored entries:
5.0+0.0im 1.0+0.0im ⋅ ⋅
⋅ 5.0+0.0im 1.41421+0.0im ⋅
⋅ ⋅ 5.0+0.0im 1.73205+0.0im
⋅ ⋅ ⋅ 5.0+0.0im
a' * a
Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=true
4×4 SparseMatrixCSC{ComplexF64, Int64} with 3 stored entries:
⋅ ⋅ ⋅ ⋅
⋅ 1.0+0.0im ⋅ ⋅
⋅ ⋅ 2.0+0.0im ⋅
⋅ ⋅ ⋅ 3.0+0.0im
a ^ 3
Quantum Object: type=Operator dims=[4] size=(4, 4) ishermitian=false
4×4 SparseMatrixCSC{ComplexF64, Int64} with 1 stored entry:
⋅ ⋅ ⋅ 2.44949+0.0im
⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅
⋅ ⋅ ⋅ ⋅
x = sigmax()
Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true
2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries:
⋅ 1.0+0.0im
1.0+0.0im ⋅
x / sqrt(2)
Quantum Object: type=Operator dims=[2] size=(2, 2) ishermitian=true
2×2 SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries:
⋅ 0.707107+0.0im
0.707107+0.0im ⋅
x ^ 3 == x
true
# type \approx + <TAB> to get symbol ≈
x ^ 3 ≈ x
true
Of course, like matrices, multiplying two objects of incompatible dims
or size
throws an error:
julia> a * x
ERROR: DimensionMismatch: The quantum objects should have the same Hilbert `dimensions`.
Note that there is a special case for multiplying Ket
and Bra
, which results in outer product
u = Qobj([1, 2, 3])
Quantum Object: type=Ket dims=[3] size=(3,)
3-element Vector{Int64}:
1
2
3
v = Qobj([4, 5, 6])
v'
Quantum Object: type=Bra dims=[3] size=(1, 3)
1×3 adjoint(::Vector{Int64}) with eltype Int64:
4 5 6
u * v'
Quantum Object: type=Operator dims=[3] size=(3, 3) ishermitian=false
3×3 Matrix{Int64}:
4 5 6
8 10 12
12 15 18
Of course, if you switch the order of multiplication, it becomes inner product
v' * u
32