Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support time-dependent liouvillian #277

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/QuantumToolbox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import SciMLOperators:
AbstractSciMLOperator,
MatrixOperator,
ScalarOperator,
ScaledOperator,
AddedOperator,
IdentityOperator,
cache_operator,
update_coefficients!,
Expand Down
59 changes: 34 additions & 25 deletions src/qobj/superoperators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
Functions for generating (common) quantum super-operators.
=#

export spre, spost, sprepost, lindblad_dissipator
export spre, spost, sprepost, liouvillian, lindblad_dissipator

# intrinsic functions for super-operators
# (keep these because they take AbstractMatrix as input)
# (keep these because they take AbstractMatrix as input and ensure the output is sparse matrix)
_spre(A::AbstractMatrix, Id::AbstractMatrix) = kron(Id, sparse(A))
_spre(A::AbstractSparseMatrix, Id::AbstractMatrix) = kron(Id, A)
_spost(B::AbstractMatrix, Id::AbstractMatrix) = kron(transpose(sparse(B)), Id)
Expand All @@ -14,9 +14,22 @@ _sprepost(A::AbstractMatrix, B::AbstractMatrix) = kron(transpose(sparse(B)), spa
_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)
_liouvillian(H::AbstractMatrix, Id::AbstractMatrix) = -1im * (_spre(H, Id) - _spost(H, Id))

# (if input is AbstractSciMLOperator)
_spre(A::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spre(A.A, Id))
_spre(A::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(A.λ, _spre(A.L, Id))
_spre(A::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spre(op, Id), +, A.ops)
_spost(B::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_spost(B.A, Id))
_spost(B::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(B.λ, _spost(B.L, Id))
_spost(B::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _spost(op, Id), +, B.ops)
_liouvillian(H::MatrixOperator, Id::AbstractMatrix) = MatrixOperator(_liouvillian(H.A, Id))
_liouvillian(H::ScaledOperator, Id::AbstractMatrix) = ScaledOperator(H.λ, _liouvillian(H.L, Id))
_liouvillian(H::AddedOperator, Id::AbstractMatrix) = mapreduce(op -> _liouvillian(op, Id), +, H.ops)
# TODO: support `_sprepost`, `sprepost`, and `lindblad_dissipator` for AbstractSciMLOperator (allow c_ops with Vector{QobjEvo})

@doc raw"""
spre(A::QuantumObject, Id_cache=I(size(A,1)))
spre(A::AbstractQuantumObject, 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}``.

Expand All @@ -27,14 +40,13 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r
```
(see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details)

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.
"""
spre(A::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {T} =
QuantumObject(_spre(A.data, Id_cache), SuperOperator, A.dims)
spre(A::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(A, 1))) where {DT} =
get_typename_wrapper(A)(_spre(A.data, Id_cache), SuperOperator, A.dims)

@doc raw"""
spost(B::QuantumObject, Id_cache=I(size(B,1)))
spost(B::AbstractQuantumObject, 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}``.

Expand All @@ -45,11 +57,10 @@ Since the density matrix is vectorized in [`OperatorKet`](@ref) form: ``|\hat{\r
```
(see the section in documentation: [Superoperators and Vectorized Operators](@ref doc:Superoperators-and-Vectorized-Operators) for more details)

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.
"""
spost(B::QuantumObject{<:AbstractArray{T},OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {T} =
QuantumObject(_spost(B.data, Id_cache), SuperOperator, B.dims)
spost(B::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(B, 1))) where {DT} =
get_typename_wrapper(B)(_spost(B.data, Id_cache), SuperOperator, B.dims)

@doc raw"""
sprepost(A::QuantumObject, B::QuantumObject)
Expand Down Expand Up @@ -84,21 +95,21 @@ Returns the Lindblad [`SuperOperator`](@ref) defined as
\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.
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).
See also [`spre`](@ref), [`spost`](@ref), and [`sprepost`](@ref).
"""
function lindblad_dissipator(O::QuantumObject{DT,OperatorQuantumObject}, Id_cache = I(size(O, 1))) where {DT}
Od_O = O' * O
return sprepost(O, O') - spre(Od_O, Id_cache) / 2 - spost(Od_O, Id_cache) / 2
return sprepost(O, O') - (spre(Od_O, Id_cache) + spost(Od_O, Id_cache)) / 2
end
# TODO: suppport collapse operator given as QobjEvo-type

# It is already a SuperOperator
lindblad_dissipator(O::QuantumObject{DT,SuperOperatorQuantumObject}, Id_cache = nothing) where {DT} = O

@doc raw"""
liouvillian(H::QuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims)))
liouvillian(H::AbstractQuantumObject, c_ops::Union{Nothing,AbstractVector,Tuple}=nothing, Id_cache=I(prod(H.dims)))

Construct the Liouvillian [`SuperOperator`](@ref) for a system Hamiltonian ``\hat{H}`` and a set of collapse operators ``\{\hat{C}_n\}_n``:

Expand All @@ -117,20 +128,18 @@ The optional argument `Id_cache` can be used to pass a precomputed identity matr
See also [`spre`](@ref), [`spost`](@ref), and [`lindblad_dissipator`](@ref).
"""
function liouvillian(
H::QuantumObject{MT1,OpType1},
H::AbstractQuantumObject{DT,OpType},
c_ops::Union{Nothing,AbstractVector,Tuple} = nothing,
Id_cache = I(prod(H.dims)),
) where {MT1<:AbstractMatrix,OpType1<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}}
) where {DT,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}}
L = liouvillian(H, Id_cache)
if !(c_ops isa Nothing)
for c_op in c_ops
L += lindblad_dissipator(c_op, Id_cache)
end
L += mapreduce(c_op -> lindblad_dissipator(c_op), +, c_ops)
ytdHuang marked this conversation as resolved.
Show resolved Hide resolved
end
return L
end

liouvillian(H::QuantumObject{<:AbstractMatrix,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dims))) =
-1im * (spre(H, Id_cache) - spost(H, Id_cache))
liouvillian(H::AbstractQuantumObject{DT,OperatorQuantumObject}, Id_cache::Diagonal = I(prod(H.dims))) where {DT} =
get_typename_wrapper(H)(_liouvillian(H.data, Id_cache), SuperOperator, H.dims)

liouvillian(H::QuantumObject{<:AbstractMatrix,SuperOperatorQuantumObject}, Id_cache::Diagonal) = H
liouvillian(H::AbstractQuantumObject{DT,SuperOperatorQuantumObject}, Id_cache::Diagonal) where {DT} = H
18 changes: 1 addition & 17 deletions src/time_evolution/mesolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,7 @@ function _generate_mesolve_kwargs(e_ops, progress_bar::Val{false}, tlist, kwargs
end

_mesolve_make_L_QobjEvo(H::QuantumObject, c_ops) = QobjEvo(liouvillian(H, c_ops); type = SuperOperator)
function _mesolve_make_L_QobjEvo(H::Tuple, c_ops)
c_ops isa Nothing && return QobjEvo(H; type = SuperOperator, f = liouvillian)
return QobjEvo((H..., mapreduce(op -> lindblad_dissipator(op), +, c_ops)); type = SuperOperator, f = liouvillian)
end
_mesolve_make_L_QobjEvo(H::QuantumObjectEvolution{DT,OperatorQuantumObject}, c_ops) where {DT<:AbstractSciMLOperator} =
throw(
ArgumentError(
"This function does not support the data type of time-dependent Operator `H` currently. Try to provide `H` as a time-dependent SuperOperator or Tuple instead.",
),
)
function _mesolve_make_L_QobjEvo(
H::QuantumObjectEvolution{DT,SuperOperatorQuantumObject},
c_ops,
) where {DT<:AbstractSciMLOperator}
c_ops isa Nothing && return H
return H + QobjEvo((mapreduce(op -> lindblad_dissipator(op), +, c_ops)))
end
_mesolve_make_L_QobjEvo(H::Union{QuantumObjectEvolution,Tuple}, c_ops) = liouvillian(QobjEvo(H), c_ops)
ytdHuang marked this conversation as resolved.
Show resolved Hide resolved

@doc raw"""
mesolveProblem(
Expand Down
2 changes: 1 addition & 1 deletion src/time_evolution/time_evolution.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export TimeEvolutionSol, TimeEvolutionMCSol, TimeEvolutionSSESol

export liouvillian, liouvillian_floquet, liouvillian_generalized
export liouvillian_floquet, liouvillian_generalized

const DEFAULT_ODE_SOLVER_OPTIONS = (abstol = 1e-8, reltol = 1e-6, save_everystep = false, save_end = true)
const DEFAULT_SDE_SOLVER_OPTIONS = (abstol = 1e-2, reltol = 1e-2, save_everystep = false, save_end = true)
Expand Down
7 changes: 6 additions & 1 deletion test/core-test/quantum_objects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,15 @@
end

@testset "Operator and SuperOperator" begin
N = 10
A = Qobj(rand(ComplexF64, N, N))
B = Qobj(rand(ComplexF64, N, N))
ρ = rand_dm(N) # random density matrix
@test mat2vec(A * ρ * B) ≈ spre(A) * spost(B) * mat2vec(ρ) ≈ sprepost(A, B) * mat2vec(ρ) # we must make sure this equality holds !

a = sprand(ComplexF64, 100, 100, 0.1)
a2 = Qobj(a)
a3 = Qobj(a, type = SuperOperator)

@test isket(a2) == false
@test isbra(a2) == false
@test isoper(a2) == true
Expand Down
40 changes: 26 additions & 14 deletions test/core-test/quantum_objects_evo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -160,25 +160,37 @@
# @test X_warn == X3
# end

@testset "Time Dependent Operators" begin
@testset "Time Dependent Operators and SuperOperators" begin
N = 10
a = destroy(N)
coef1(p, t) = exp(-1im * p.ω1 * t)
coef2(p, t) = sin(p.ω2 * t)

@test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]])

op1 = QobjEvo(((a, coef1), a' * a, (a', coef2)))
op1 = QobjEvo(((a, coef1), a' * a, (a', coef2)))

p = (ω1 = 1, ω2 = 2)
@test op1(p, 0.1) ≈ coef1(p, 0.1) * a + a' * a + coef2(p, 0.1) * a'

ψ = fock(N, 1)
@test op1(ψ, p, 0.1) ≈ (coef1(p, 0.1) * a + a' * a + coef2(p, 0.1) * a') * ψ

t = rand()
p = (ω1 = rand(), ω2 = rand())

# Operator
H_td = QobjEvo(((a, coef1), a' * a, (a', coef2)))
H_ti = coef1(p, t) * a + a' * a + coef2(p, t) * a'
ψ = rand_ket(N)
@test H_td(p, t) ≈ H_ti
@test H_td(ψ, p, t) ≈ H_ti * ψ
@test isconstant(a) == true
@test isconstant(op1) == false
@test isconstant(H_td) == false
@test isconstant(Qobj(a)) == true
ytdHuang marked this conversation as resolved.
Show resolved Hide resolved
@test isoper(H_td) == true

# SuperOperator
c_ops = [sqrt(rand()) * a]
L_td = liouvillian(H_td, c_ops)
L_td2 = -1im * spre(H_td) + 1im * spost(H_td) + lindblad_dissipator(c_ops[1])
ρvec = mat2vec(rand_dm(N))
@test L_td(p, t) ≈ L_td2(p, t)
@test L_td(ρvec, p, t) ≈ L_td2(ρvec, p, t)
@test isconstant(L_td) == false
@test issuper(L_td) == true

@test_throws MethodError QobjEvo([[a, coef1], a' * a, [a', coef2]])
@test_throws ArgumentError H_td(ρvec, p, t)
@test_throws ArgumentError L_td(ψ, p, t)
end
end
12 changes: 2 additions & 10 deletions test/core-test/time_evolution.jl
Original file line number Diff line number Diff line change
Expand Up @@ -205,18 +205,9 @@
# @test sol_sse.expect ≈ sol_sse_td.expect atol = 1e-2 * length(tlist)

H_td2 = QobjEvo(H_td)
L_td = QobjEvo(H_td, type = SuperOperator, f = liouvillian)
L_td = liouvillian(H_td2)

sol_se_td2 = sesolve(H_td2, ψ0, tlist, e_ops = e_ops, progress_bar = Val(false), params = p)
@test_throws ArgumentError mesolve(
H_td2,
ψ0,
tlist,
c_ops,
e_ops = e_ops,
progress_bar = Val(false),
params = p,
)
sol_me_td2 = mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p)
sol_mc_td2 = mcsolve(
H_td2,
Expand Down Expand Up @@ -253,6 +244,7 @@
@inferred mesolve(H, ψ0, tlist, c_ops, e_ops = e_ops, saveat = tlist, progress_bar = Val(false))
@inferred mesolve(H, ψ0, tlist, (a, a'), e_ops = (a' * a, a'), progress_bar = Val(false)) # We test the type inference for Tuple
@inferred mesolve(H_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p)
@inferred mesolve(H_td2, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p)
@inferred mesolve(L_td, ψ0, tlist, c_ops, e_ops = e_ops, progress_bar = Val(false), params = p)
end

Expand Down