Skip to content

Commit

Permalink
Merge pull request #162 from ytdHuang/dev/superoperator
Browse files Browse the repository at this point in the history
Always return sparse matrices for `spre`, `spost`, and `sprepost`
  • Loading branch information
albertomercurio authored Jun 10, 2024
2 parents 39b436d + f8e90de commit 0d9cf1b
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 84 deletions.
1 change: 1 addition & 0 deletions src/QuantumToolbox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ include("qobj/eigsolve.jl")
include("qobj/functions.jl")
include("qobj/states.jl")
include("qobj/operators.jl")
include("qobj/superoperators.jl")
include("qobj/synonyms.jl")

# time evolution
Expand Down
12 changes: 6 additions & 6 deletions src/qobj/boolean_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,45 +7,45 @@ export isket, isbra, isoper, isoperbra, isoperket, issuper
@doc raw"""
isbra(A::QuantumObject)
Checks if the [`QuantumObject`](@ref) `A` is a [`BraQuantumObject`](@ref) state.
Checks if the [`QuantumObject`](@ref) `A` is a [`BraQuantumObject`](@ref).
"""
isbra(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} = OpType <: BraQuantumObject

@doc raw"""
isket(A::QuantumObject)
Checks if the [`QuantumObject`](@ref) `A` is a [`KetQuantumObject`](@ref) state.
Checks if the [`QuantumObject`](@ref) `A` is a [`KetQuantumObject`](@ref).
"""
isket(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} = OpType <: KetQuantumObject

@doc raw"""
isoper(A::QuantumObject)
Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorQuantumObject`](@ref) state.
Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorQuantumObject`](@ref).
"""
isoper(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} =
OpType <: OperatorQuantumObject

@doc raw"""
isoperbra(A::QuantumObject)
Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorBraQuantumObject`](@ref) state.
Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorBraQuantumObject`](@ref).
"""
isoperbra(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} =
OpType <: OperatorBraQuantumObject

@doc raw"""
isoperket(A::QuantumObject)
Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorKetQuantumObject`](@ref) state.
Checks if the [`QuantumObject`](@ref) `A` is a [`OperatorKetQuantumObject`](@ref).
"""
isoperket(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} =
OpType <: OperatorKetQuantumObject

@doc raw"""
issuper(A::QuantumObject)
Checks if the [`QuantumObject`](@ref) `A` is a [`SuperOperatorQuantumObject`](@ref) state.
Checks if the [`QuantumObject`](@ref) `A` is a [`SuperOperatorQuantumObject`](@ref).
"""
issuper(A::QuantumObject{<:AbstractArray{T},OpType}) where {T,OpType<:QuantumObjectType} =
OpType <: SuperOperatorQuantumObject
Expand Down
75 changes: 0 additions & 75 deletions src/qobj/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export sigmam, sigmap, sigmax, sigmay, sigmaz
export destroy, create, eye, projection
export fdestroy, fcreate
export commutator
export spre, spost, sprepost, lindblad_dissipator

@doc raw"""
commutator(A::QuantumObject, B::QuantumObject; anti::Bool=false)
Expand All @@ -24,80 +23,6 @@ commutator(
anti::Bool = false,
) where {T1,T2} = A * B - (-1)^anti * B * A

@doc raw"""
spre(A::QuantumObject, Id_cache=I(size(A,1)))
Returns the [`SuperOperator`](@ref) form of `A` acting on the left of the density matrix operator: ``\mathcal{O} \left(\hat{A}\right) \left[ \hat{\rho} \right] = \hat{A} \hat{\rho}``.
Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\rho}\rangle\rangle``, this [`SuperOperator`](@ref) is always a matrix ``\hat{\mathbb{1}} \otimes \hat{A}``, namely
```math
\mathcal{O} \left(\hat{A}\right) \left[ \hat{\rho} \right] = \hat{\mathbb{1}} \otimes \hat{A} ~ |\hat{\rho}\rangle\rangle
```
The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when
the same function is applied multiple times with a known Hilbert space dimension.
"""
spre(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {T} =
QuantumObject(kron(Id_cache, A.data), SuperOperator, A.dims)

@doc raw"""
spost(B::QuantumObject)
Returns the [`SuperOperator`](@ref) form of `B` acting on the right of the density matrix operator: ``\mathcal{O} \left(\hat{B}\right) \left[ \hat{\rho} \right] = \hat{\rho} \hat{B}``.
Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\rho}\rangle\rangle``, this [`SuperOperator`](@ref) is always a matrix ``\hat{B}^T \otimes \hat{\mathbb{1}}``, namely
```math
\mathcal{O} \left(\hat{B}\right) \left[ \hat{\rho} \right] = \hat{B}^T \otimes \hat{\mathbb{1}} ~ |\hat{\rho}\rangle\rangle
```
The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when
the same function is applied multiple times with a known Hilbert space dimension.
"""
spost(B::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {T} =
QuantumObject(kron(sparse(transpose(sparse(B.data))), Id_cache), SuperOperator, B.dims) # TODO: fix the sparse conversion

@doc raw"""
sprepost(A::QuantumObject, B::QuantumObject)
Returns the [`SuperOperator`](@ref) form of `A` and `B` acting on the left and right of the density matrix operator, respectively: ``\mathcal{O} \left( \hat{A}, \hat{B} \right) \left[ \hat{\rho} \right] = \hat{A} \hat{\rho} \hat{B}``.
Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\rho}\rangle\rangle``, this [`SuperOperator`](@ref) is always a matrix ``\hat{B}^T \otimes \hat{A}``, namely
```math
\mathcal{O} \left(\hat{A}, \hat{B}\right) \left[ \hat{\rho} \right] = \hat{B}^T \otimes \hat{A} ~ |\hat{\rho}\rangle\rangle = \textrm{spre}(A) * \textrm{spost}(B) ~ |\hat{\rho}\rangle\rangle
```
"""
sprepost(
A::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject},
B::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject},
) where {T1,T2} = QuantumObject(kron(sparse(transpose(sparse(B.data))), A.data), SuperOperator, A.dims) # TODO: fix the sparse conversion

@doc raw"""
lindblad_dissipator(O::QuantumObject, Id_cache=I(size(O,1))
Returns the Lindblad [`SuperOperator`](@ref) defined as
```math
\mathcal{D} \left( \hat{O} \right) \left[ \hat{\rho} \right] = \frac{1}{2} \left( 2 \hat{O} \hat{\rho} \hat{O}^\dagger -
\hat{O}^\dagger \hat{O} \hat{\rho} - \hat{\rho} \hat{O}^\dagger \hat{O} \right)
```
The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when
the same function is applied multiple times with a known Hilbert space dimension.
"""
function lindblad_dissipator(
O::QuantumObject{<:AbstractArray{T},OperatorQuantumObject},
Id_cache = I(size(O, 1)),
) where {T}
Od_O = O' * O
return sprepost(O, O') - spre(Od_O, Id_cache) / 2 - spost(Od_O, Id_cache) / 2
end

# It is already a SuperOperator
lindblad_dissipator(O::QuantumObject{<:AbstractArray{T},SuperOperatorQuantumObject}, Id_cache) where {T} = O

@doc raw"""
destroy(N::Int)
Expand Down
106 changes: 106 additions & 0 deletions src/qobj/superoperators.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#=
Functions for generating (common) quantum super-operators.
=#

export spre, spost, sprepost, lindblad_dissipator

# intrinsic functions for super-operators
_spre(A::AbstractMatrix, Id::AbstractMatrix) = kron(Id, sparse(A))
_spre(A::AbstractSparseMatrix, Id::AbstractMatrix) = kron(Id, A)
if VERSION < v"1.10"
_spost(B::AbstractMatrix, Id::AbstractMatrix) = kron(sparse(transpose(sparse(B))), Id)
_spost(B::AbstractSparseMatrix, Id::AbstractMatrix) = kron(sparse(transpose(B)), Id)
_sprepost(A::AbstractMatrix, B::AbstractMatrix) = kron(sparse(transpose(sparse(B))), sparse(A))
_sprepost(A::AbstractMatrix, B::AbstractSparseMatrix) = kron(sparse(transpose(B)), sparse(A))
_sprepost(A::AbstractSparseMatrix, B::AbstractMatrix) = kron(sparse(transpose(sparse(B))), A)
_sprepost(A::AbstractSparseMatrix, B::AbstractSparseMatrix) = kron(sparse(transpose(B)), A)
else
_spost(B::AbstractMatrix, Id::AbstractMatrix) = kron(transpose(sparse(B)), Id)
_spost(B::AbstractSparseMatrix, Id::AbstractMatrix) = kron(transpose(B), Id)
_sprepost(A::AbstractMatrix, B::AbstractMatrix) = kron(transpose(sparse(B)), sparse(A))
_sprepost(A::AbstractMatrix, B::AbstractSparseMatrix) = kron(transpose(B), sparse(A))
_sprepost(A::AbstractSparseMatrix, B::AbstractMatrix) = kron(transpose(sparse(B)), A)
_sprepost(A::AbstractSparseMatrix, B::AbstractSparseMatrix) = kron(transpose(B), A)
end

@doc raw"""
spre(A::QuantumObject, Id_cache=I(size(A,1)))
Returns the [`SuperOperator`](@ref) form of `A` acting on the left of the density matrix operator: ``\mathcal{O} \left(\hat{A}\right) \left[ \hat{\rho} \right] = \hat{A} \hat{\rho}``.
Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\rho}\rangle\rangle``, this [`SuperOperator`](@ref) is always a matrix ``\hat{\mathbb{1}} \otimes \hat{A}``, namely
```math
\mathcal{O} \left(\hat{A}\right) \left[ \hat{\rho} \right] = \hat{\mathbb{1}} \otimes \hat{A} ~ |\hat{\rho}\rangle\rangle
```
The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when
the same function is applied multiple times with a known Hilbert space dimension.
"""
spre(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {T} =
QuantumObject(_spre(A.data, Id_cache), SuperOperator, A.dims)

@doc raw"""
spost(B::QuantumObject, Id_cache=I(size(B,1)))
Returns the [`SuperOperator`](@ref) form of `B` acting on the right of the density matrix operator: ``\mathcal{O} \left(\hat{B}\right) \left[ \hat{\rho} \right] = \hat{\rho} \hat{B}``.
Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\rho}\rangle\rangle``, this [`SuperOperator`](@ref) is always a matrix ``\hat{B}^T \otimes \hat{\mathbb{1}}``, namely
```math
\mathcal{O} \left(\hat{B}\right) \left[ \hat{\rho} \right] = \hat{B}^T \otimes \hat{\mathbb{1}} ~ |\hat{\rho}\rangle\rangle
```
The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when
the same function is applied multiple times with a known Hilbert space dimension.
"""
spost(B::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {T} =
QuantumObject(_spost(B.data, Id_cache), SuperOperator, B.dims)

@doc raw"""
sprepost(A::QuantumObject, B::QuantumObject)
Returns the [`SuperOperator`](@ref) form of `A` and `B` acting on the left and right of the density matrix operator, respectively: ``\mathcal{O} \left( \hat{A}, \hat{B} \right) \left[ \hat{\rho} \right] = \hat{A} \hat{\rho} \hat{B}``.
Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\rho}\rangle\rangle``, this [`SuperOperator`](@ref) is always a matrix ``\hat{B}^T \otimes \hat{A}``, namely
```math
\mathcal{O} \left(\hat{A}, \hat{B}\right) \left[ \hat{\rho} \right] = \hat{B}^T \otimes \hat{A} ~ |\hat{\rho}\rangle\rangle = \textrm{spre}(A) * \textrm{spost}(B) ~ |\hat{\rho}\rangle\rangle
```
See also [`spre`](@ref) and [`spost`](@ref).
"""
function sprepost(
A::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject},
B::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject},
) where {T1,T2}
A.dims != B.dims && throw(DimensionMismatch("The two quantum objects are not of the same Hilbert dimension."))

return QuantumObject(_sprepost(A.data, B.data), SuperOperator, A.dims)
end

@doc raw"""
lindblad_dissipator(O::QuantumObject, Id_cache=I(size(O,1))
Returns the Lindblad [`SuperOperator`](@ref) defined as
```math
\mathcal{D} \left( \hat{O} \right) \left[ \hat{\rho} \right] = \frac{1}{2} \left( 2 \hat{O} \hat{\rho} \hat{O}^\dagger -
\hat{O}^\dagger \hat{O} \hat{\rho} - \hat{\rho} \hat{O}^\dagger \hat{O} \right)
```
The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when
the same function is applied multiple times with a known Hilbert space dimension.
See also [`spre`](@ref) and [`spost`](@ref).
"""
function lindblad_dissipator(
O::QuantumObject{<:AbstractArray{T},OperatorQuantumObject},
Id_cache = I(size(O, 1)),
) where {T}
Od_O = O' * O
return sprepost(O, O') - spre(Od_O, Id_cache) / 2 - spost(Od_O, Id_cache) / 2
end

# It is already a SuperOperator
lindblad_dissipator(O::QuantumObject{<:AbstractArray{T},SuperOperatorQuantumObject}, Id_cache) where {T} = O
7 changes: 4 additions & 3 deletions src/time_evolution/time_evolution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ end

### LIOUVILLIAN ###
@doc raw"""
liouvillian(H::QuantumObject, c_ops::AbstractVector, Id_cache=I(prod(H.dims))
liouvillian(H::QuantumObject, c_ops::AbstractVector, Id_cache=I(prod(H.dims)))
Construct the Liouvillian [`SuperOperator`](@ref) for a system Hamiltonian ``\hat{H}`` and a set of collapse operators ``\hat{O}_i``:
Expand All @@ -130,8 +130,9 @@ where
\mathcal{D}(\hat{O}_i) [\cdot] = \hat{O}_i [\cdot] \hat{O}_i^\dagger - \frac{1}{2} \hat{O}_i^\dagger \hat{O}_i [\cdot] - \frac{1}{2} [\cdot] \hat{O}_i^\dagger \hat{O}_i
```
The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when
the same function is applied multiple times with a known Hilbert space dimension.
The optional argument `Id_cache` can be used to pass a precomputed identity matrix. This can be useful when the same function is applied multiple times with a known Hilbert space dimension.
See also [`spre`](@ref), [`spost`](@ref), and [`lindblad_dissipator`](@ref).
"""
function liouvillian(
H::QuantumObject{MT1,OpType1},
Expand Down
17 changes: 17 additions & 0 deletions test/states_and_operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,21 @@
@test_throws DimensionMismatch qeye(4, dims = [2])
@test_throws DimensionMismatch qeye(2, type = SuperOperator)
@test_throws DimensionMismatch qeye(4, type = SuperOperator, dims = [2, 2])

# spre, spost, and sprepost
Xs = sigmax()
Xd = sparse_to_dense(Xs)
A_wrong1 = Qobj(rand(4, 4), dims = [4])
A_wrong2 = Qobj(rand(4, 4), dims = [2, 2])
A_wrong3 = Qobj(rand(3, 3))
@test (typeof(spre(Xd).data) <: SparseMatrixCSC) == true
@test (typeof(spre(Xs).data) <: SparseMatrixCSC) == true
@test (typeof(spost(Xd).data) <: SparseMatrixCSC) == true
@test (typeof(spost(Xs).data) <: SparseMatrixCSC) == true
@test (typeof(sprepost(Xd, Xd).data) <: SparseMatrixCSC) == true
@test (typeof(sprepost(Xs, Xs).data) <: SparseMatrixCSC) == true
@test (typeof(sprepost(Xs, Xd).data) <: SparseMatrixCSC) == true
@test (typeof(sprepost(Xd, Xs).data) <: SparseMatrixCSC) == true
@test_throws DimensionMismatch sprepost(A_wrong1, A_wrong2)
@test_throws DimensionMismatch sprepost(A_wrong1, A_wrong3)
end

0 comments on commit 0d9cf1b

Please sign in to comment.