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
5We 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
5Qobj([1 2 3 4 5])
Quantum Object: type=Bra() dims=[5] size=(1, 5)
1×5 Matrix{Int64}:
1 2 3 4 5Qobj(rand(4, 4))
Quantum Object: type=Operator() dims=[4] size=(4, 4) ishermitian=false
4×4 Matrix{Float64}:
0.901017 0.78394 0.310201 0.934144
0.727505 0.734225 0.623318 0.94498
0.00112226 0.874157 0.689751 0.926694
0.882291 0.00253095 0.955623 0.0240171M = rand(ComplexF64, 4, 4)
Qobj(M, dims = [2, 2]) # dims as Vector
Qobj(M, dims = (2, 2)) # dims as Tuple (recommended)
import QuantumToolbox: SVector # or using StaticArrays
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.0405918+0.740482im 0.265694+0.226557im … 0.522413+0.915642im
0.399653+0.202682im 0.704229+0.817846im 0.150042+0.853891im
0.998463+0.25546im 0.328151+0.706325im 0.0993008+0.736577im
0.598105+0.042577im 0.130849+0.677341im 0.552103+0.485004imBeware 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.85938 0.354344 0.521071 0.0782242
0.564546 0.828351 0.374472 0.718698
0.0569319 0.741632 0.971696 0.410428
0.366729 0.19876 0.470253 0.249823Difference 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 matrixenr_fock: Fock state in the excitation number restricted (ENR) spaceenr_thermal_dm: thermal state in the excitation number restricted (ENR) spacespin_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-joperatorspin_Jx:operator for Spin- jspin_Jy:operator for Spin- jspin_Jz:operator for Spin- jspin_Jm:operator for Spin- jspin_Jp:operator for Spin- jspin_J_set: a set of Spin-joperatorsfdestroy: fermion destruction operatorfcreate: fermion creation operatorenr_destroy: destruction operator in the excitation number restricted (ENR) spaceenr_identity: identity operator in the excitation number restricted (ENR) spacecommutator: 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.0imcoherent(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.0imdestroy(4)
Quantum Object: type=Operator() dims=[4] size=(4, 4) ishermitian=false
4×4 SparseArrays.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 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries:
1.0+0.0im ⋅
⋅ -1.0+0.0imQobj 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.data4×4 SparseArrays.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.0ima.typeOperator()a.dims1-element StaticArraysCore.SVector{1, Int64} with indices SOneTo(1):
4In 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)16eltype(a) # element typeComplexF64 (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)) # unitaryfalse
false
true
false
false
false
true
false
false
false
false
falsedata 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.0imVector{Int64}(v_d)
Quantum Object: type=Ket() dims=[2] size=(2,)
2-element Vector{Int64}:
1
0using SparseArrays
v_s = SparseVector(v_d)
Quantum Object: type=Ket() dims=[2] size=(2,)
2-element SparseArrays.SparseVector{ComplexF64, Int64} with 1 stored entry:
[1] = 1.0+0.0imSparseVector{Float64}(v_s)
Quantum Object: type=Ket() dims=[2] size=(2,)
2-element SparseArrays.SparseVector{Float64, Int64} with 1 stored entry:
[1] = 1.0x_s = sigmax()
Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true
2×2 SparseArrays.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 SparseArrays.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.0To 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.0imto_sparse(x_d)
Quantum Object: type=Operator() dims=[2] size=(2, 2) ishermitian=true
2×2 SparseArrays.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 SparseArrays.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 LinearAlgebra.Adjoint{ComplexF64, SparseArrays.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 SparseArrays.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.0ima' * a
Quantum Object: type=Operator() dims=[4] size=(4, 4) ishermitian=true
4×4 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 3 stored entries:
⋅ ⋅ ⋅ ⋅
⋅ 1.0+0.0im ⋅ ⋅
⋅ ⋅ 2.0+0.0im ⋅
⋅ ⋅ ⋅ 3.0+0.0ima ^ 3
Quantum Object: type=Operator() dims=[4] size=(4, 4) ishermitian=false
4×4 SparseArrays.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 SparseArrays.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 SparseArrays.SparseMatrixCSC{ComplexF64, Int64} with 2 stored entries:
⋅ 0.707107+0.0im
0.707107+0.0im ⋅x ^ 3 == xtrue# type \approx + <TAB> to get symbol ≈
x ^ 3 ≈ xtrueOf 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
3v = Qobj([4, 5, 6])
v'
Quantum Object: type=Bra() dims=[3] size=(1, 3)
1×3 adjoint(::Vector{Int64}) with eltype Int64:
4 5 6u * v'
Quantum Object: type=Operator() dims=[3] size=(3, 3) ishermitian=false
3×3 Matrix{Int64}:
4 5 6
8 10 12
12 15 18Of course, if you switch the order of multiplication, it becomes inner product
v' * u32