Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main)

- Add functions for `brmesolve`

## [v0.31.1]
Release date: 2025-05-16

Expand Down
3 changes: 3 additions & 0 deletions docs/src/resources/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ smesolve
dfd_mesolve
liouvillian
liouvillian_generalized
bloch_redfield_tensor
brterm
brmesolve
```

### [Steady State Solvers](@id doc-API:Steady-State-Solvers)
Expand Down
1 change: 1 addition & 0 deletions src/QuantumToolbox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ include("time_evolution/callback_helpers/mcsolve_callback_helpers.jl")
include("time_evolution/callback_helpers/ssesolve_callback_helpers.jl")
include("time_evolution/callback_helpers/smesolve_callback_helpers.jl")
include("time_evolution/mesolve.jl")
include("time_evolution/brmesolve.jl")
include("time_evolution/lr_mesolve.jl")
include("time_evolution/sesolve.jl")
include("time_evolution/mcsolve.jl")
Expand Down
202 changes: 202 additions & 0 deletions src/time_evolution/brmesolve.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
export bloch_redfield_tensor, brterm, brmesolve

@doc raw"""
bloch_redfield_tensor(
H::QuantumObject{Operator},
a_ops::Union{AbstractVector, Tuple},
c_ops::Union{AbstractVector, Tuple, Nothing}=nothing;
sec_cutoff::Real=0.1,
fock_basis::Union{Val,Bool}=Val(false)
)
Calculates the Bloch-Redfield tensor ([`SuperOperator`](@ref)) for a system given a set of operators and corresponding spectral functions that describes the system's coupling to its environment.
## Arguments
- `H`: The system Hamiltonian. Must be an [`Operator`](@ref)
- `a_ops`: Nested list with each element is a `Tuple` of operator-function pairs `(a_op, spectra)`, and the coupling [`Operator`](@ref) `a_op` must be hermitian with corresponding `spectra` being a `Function` of transition energy
- `c_ops`: List of collapse operators corresponding to Lindblad dissipators
- `sec_cutoff`: Cutoff for secular approximation. Use `-1` if secular approximation is not used when evaluating bath-coupling terms.
- `fock_basis`: Whether to return the tensor in the input (fock) basis or the diagonalized (eigen) basis.
## Return
The return depends on `fock_basis`.
- `fock_basis=Val(true)`: return the Bloch-Redfield tensor (in the fock basis) only.
- `fock_basis=Val(false)`: return the Bloch-Redfield tensor (in the eigen basis) along with the transformation matrix from eigen to fock basis.
"""
function bloch_redfield_tensor(

Check warning on line 29 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L29

Added line #L29 was not covered by tests
H::QuantumObject{Operator},
a_ops::Union{AbstractVector,Tuple},
c_ops::Union{AbstractVector,Tuple,Nothing} = nothing;
sec_cutoff::Real = 0.1,
fock_basis::Union{Val,Bool} = Val(false),
)
rst = eigenstates(H)
U = QuantumObject(rst.vectors, Operator(), H.dimensions)
sec_cutoff = float(sec_cutoff)

Check warning on line 38 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L36-L38

Added lines #L36 - L38 were not covered by tests

# in fock basis
R0 = liouvillian(H, c_ops)

Check warning on line 41 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L41

Added line #L41 was not covered by tests

# set fock_basis=Val(false) and change basis together at the end
R1 = 0
if !isempty(a_ops)
R1 += mapreduce(x -> _brterm(rst, x[1], x[2], sec_cutoff, Val(false)), +, a_ops)

Check warning on line 46 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L44-L46

Added lines #L44 - L46 were not covered by tests

# do (a_op, spectra)
# _brterm(rst, a_op, spectra, sec_cutoff, Val(false))
# end
end

SU = sprepost(U, U') # transformation matrix from eigen basis back to fock basis
if getVal(fock_basis)
return R0 + SU * R1 * SU'

Check warning on line 55 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L53-L55

Added lines #L53 - L55 were not covered by tests
else
return SU' * R0 * SU + R1, U

Check warning on line 57 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L57

Added line #L57 was not covered by tests
end
end

@doc raw"""
brterm(
H::QuantumObject{Operator},
a_op::QuantumObject{Operator},
spectra::Function;
sec_cutoff::Real=0.1,
fock_basis::Union{Bool, Val}=Val(false)
)
Calculates the contribution of one coupling operator to the Bloch-Redfield tensor.
## Argument
- `H`: The system Hamiltonian. Must be an [`Operator`](@ref)
- `a_op`: The operator coupling to the environment. Must be hermitian.
- `spectra`: The corresponding environment spectra as a `Function` of transition energy.
- `sec_cutoff`: Cutoff for secular approximation. Use `-1` if secular approximation is not used when evaluating bath-coupling terms.
- `fock_basis`: Whether to return the tensor in the input (fock) basis or the diagonalized (eigen) basis.
## Return
The return depends on `fock_basis`.
- `fock_basis=Val(true)`: return the Bloch-Redfield term (in the fock basis) only.
- `fock_basis=Val(false)`: return the Bloch-Redfield term (in the eigen basis) along with the transformation matrix from eigen to fock basis.
"""
function brterm(

Check warning on line 87 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L87

Added line #L87 was not covered by tests
H::QuantumObject{Operator},
a_op::QuantumObject{Operator},
spectra::Function;
sec_cutoff::Real = 0.1,
fock_basis::Union{Bool,Val} = Val(false),
)
rst = eigenstates(H)
term = _brterm(rst, a_op, spectra, sec_cutoff, makeVal(fock_basis))
if getVal(fock_basis)
return term

Check warning on line 97 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L94-L97

Added lines #L94 - L97 were not covered by tests
else
return term, Qobj(rst.vectors, Operator(), rst.dimensions)

Check warning on line 99 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L99

Added line #L99 was not covered by tests
end
end

function _brterm(

Check warning on line 103 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L103

Added line #L103 was not covered by tests
rst::EigsolveResult,
a_op::T,
spectra::F,
sec_cutoff::Real,
fock_basis::Union{Val{true},Val{false}},
) where {T<:QuantumObject{Operator},F<:Function}
_check_br_spectra(spectra)

Check warning on line 110 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L110

Added line #L110 was not covered by tests

U, N = rst.vectors, prod(rst.dimensions)
Id = I(N)

Check warning on line 113 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L112-L113

Added lines #L112 - L113 were not covered by tests

skew = @. rst.values - rst.values' |> real
spectrum = spectra.(skew)

Check warning on line 116 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L115-L116

Added lines #L115 - L116 were not covered by tests

A_mat = U' * a_op.data * U

Check warning on line 118 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L118

Added line #L118 was not covered by tests

ac_term = (A_mat .* spectrum) * A_mat
bd_term = A_mat * (A_mat .* trans(spectrum))

Check warning on line 121 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L120-L121

Added lines #L120 - L121 were not covered by tests

if sec_cutoff != -1
m_cut = similar(skew)
map!(x -> abs(x) < sec_cutoff, m_cut, skew)
ac_term .*= m_cut
bd_term .*= m_cut

Check warning on line 127 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L123-L127

Added lines #L123 - L127 were not covered by tests

vec_skew = vec(skew)
M_cut = @. abs(vec_skew - vec_skew') < sec_cutoff

Check warning on line 130 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L129-L130

Added lines #L129 - L130 were not covered by tests
end

out =

Check warning on line 133 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L133

Added line #L133 was not covered by tests
0.5 * (
+ _sprepost(A_mat .* trans(spectrum), A_mat) + _sprepost(A_mat, A_mat .* spectrum) - _spost(ac_term, Id) -
_spre(bd_term, Id)
)

(sec_cutoff != -1) && (out .*= M_cut)

Check warning on line 139 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L139

Added line #L139 was not covered by tests

if getVal(fock_basis)
SU = _sprepost(U, U')
return QuantumObject(SU * out * SU', SuperOperator(), rst.dimensions)

Check warning on line 143 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L141-L143

Added lines #L141 - L143 were not covered by tests
else
return QuantumObject(out, SuperOperator(), rst.dimensions)

Check warning on line 145 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L145

Added line #L145 was not covered by tests
end
end

@doc raw"""
brmesolve(
H::QuantumObject{Operator},
ψ0::QuantumObject,
tlist::AbstractVector,
a_ops::Union{Nothing, AbstractVector, Tuple},
c_ops::Union{Nothing, AbstractVector, Tuple}=nothing;
sec_cutoff::Real=0.1,
e_ops::Union{Nothing, AbstractVector}=nothing,
kwargs...,
)
Solves for the dynamics of a system using the Bloch-Redfield master equation, given an input Hamiltonian, Hermitian bath-coupling terms and their associated spectral functions, as well as possible Lindblad collapse operators.
## Arguments
- `H`: The system Hamiltonian. Must be an [`Operator`](@ref)
- `ψ0`: Initial state of the system $|\psi(0)\rangle$. It can be either a [`Ket`](@ref), [`Operator`](@ref) or [`OperatorKet`](@ref).
- `tlist`: List of times at which to save either the state or the expectation values of the system.
- `a_ops`: Nested list with each element is a `Tuple` of operator-function pairs `(a_op, spectra)`, and the coupling [`Operator`](@ref) `a_op` must be hermitian with corresponding `spectra` being a `Function` of transition energy
- `c_ops`: List of collapse operators corresponding to Lindblad dissipators
- `sec_cutoff`: Cutoff for secular approximation. Use `-1` if secular approximation is not used when evaluating bath-coupling terms.
- `e_ops`: List of operators for which to calculate expectation values. It can be either a `Vector` or a `Tuple`.
- `kwargs`: Keyword arguments for [`mesolve`](@ref).
## Notes
- This function will automatically generate the [`bloch_redfield_tensor`](@ref) and solve the time evolution with [`mesolve`](@ref).
# Returns
- `sol::TimeEvolutionSol`: The solution of the time evolution. See also [`TimeEvolutionSol`](@ref)
"""
function brmesolve(

Check warning on line 182 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L182

Added line #L182 was not covered by tests
H::QuantumObject{Operator},
ψ0::QuantumObject,
tlist::AbstractVector,
a_ops::Union{Nothing,AbstractVector,Tuple},
c_ops::Union{Nothing,AbstractVector,Tuple} = nothing;
sec_cutoff::Real = 0.1,
e_ops::Union{Nothing,AbstractVector} = nothing,
kwargs...,
)
R = bloch_redfield_tensor(H, a_ops, c_ops; sec_cutoff = sec_cutoff, fock_basis = Val(true))

Check warning on line 192 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L192

Added line #L192 was not covered by tests

return mesolve(R, ψ0, tlist, nothing; e_ops = e_ops, kwargs...)

Check warning on line 194 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L194

Added line #L194 was not covered by tests
end

function _check_br_spectra(f::Function)
meths = methods(f, [Real])
length(meths.ms) == 0 &&

Check warning on line 199 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L197-L199

Added lines #L197 - L199 were not covered by tests
throw(ArgumentError("The following function must accept one argument: `$(meths.mt.name)(ω)` with ω<:Real"))
return nothing

Check warning on line 201 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L201

Added line #L201 was not covered by tests
end
115 changes: 115 additions & 0 deletions test/core-test/brmesolve.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
@testitem "Bloch-Redfield tensor sec_cutoff" begin
N = 5
H = num(N)
a = destroy(N)
A_op = a+a'
spectra(x) = (x>0) * 0.5
for sec_cutoff in [0, 0.1, 1, 3, -1]
R = bloch_redfield_tensor(H, [(A_op, spectra)], [a^2], sec_cutoff = sec_cutoff, fock_basis = true)
R_eig, evecs = bloch_redfield_tensor(H, [(A_op, spectra)], [a^2], sec_cutoff = sec_cutoff, fock_basis = false)
@test isa(R, QuantumObject)
@test isa(R_eig, QuantumObject)
@test isa(evecs, QuantumObject)

state = rand_dm(N) |> mat2vec
fock_computed = R * state
eig_computed = (sprepost(evecs, evecs') * R_eig * sprepost(evecs', evecs)) * state
@test isapprox(fock_computed, eig_computed, atol = 1e-15)
end
end

@testitem "Compare brterm and Lindblad" begin
N = 5
H = num(N)
a = destroy(N) + destroy(N)^2/2
A_op = a+a'
spectra(x) = x>0

# this test applies for limited cutoff
lindblad = lindblad_dissipator(a)
computation = brterm(H, A_op, spectra, sec_cutoff = 1.5, fock_basis = true)
@test isapprox(lindblad, computation, atol = 1e-15)
end

@testitem "brterm basis" begin
N = 5
H = num(N)
a = destroy(N) + destroy(N)^2/2
A_op = a+a'
spectra(x) = x>0
for sec_cutoff in [0, 0.1, 1, 3, -1]
R = brterm(H, A_op, spectra, sec_cutoff = sec_cutoff, fock_basis = true)
R_eig, evecs = brterm(H, A_op, spectra, sec_cutoff = sec_cutoff, fock_basis = false)
@test isa(R, QuantumObject)
@test isa(R_eig, QuantumObject)
@test isa(evecs, QuantumObject)

state = rand_dm(N) |> mat2vec
fock_computed = R * state
eig_computed = (sprepost(evecs, evecs') * R_eig * sprepost(evecs', evecs)) * state
@test isapprox(fock_computed, eig_computed, atol = 1e-15)
end;
end

@testitem "brterm sprectra function" begin
f(x) = exp(x)/10
function g(x)
nbar = n_thermal(abs(x), 1)
if x > 0
return nbar
elseif x < 0
return 1 + nbar
else
return 0.0
end
end

spectra_list = [
x -> (x>0), # positive frequency filter
x -> one(x), # no filter
f, # smooth filter
g, # thermal field
]

N = 5
H = num(N)
a = destroy(N) + destroy(N)^2/2
A_op = a+a'
for spectra in spectra_list
R = brterm(H, A_op, spectra, sec_cutoff = 0.1, fock_basis = true)
R_eig, evecs = brterm(H, A_op, spectra, sec_cutoff = 0.1, fock_basis = false)
@test isa(R, QuantumObject)
@test isa(R_eig, QuantumObject)
@test isa(evecs, QuantumObject)

state = rand_dm(N) |> mat2vec
fock_computed = R * state
eig_computed = (sprepost(evecs, evecs') * R_eig * sprepost(evecs', evecs)) * state
@test isapprox(fock_computed, eig_computed, atol = 1e-15)
end
end

@testitem "simple qubit system" begin
pauli_vectors = [sigmax(), sigmay(), sigmaz()]
γ = 0.25
spectra(x) = γ * (x>=0)
_m_c_op = √γ * sigmam()
_z_c_op = √γ * sigmaz()
_x_a_op = (sigmax(), spectra)

arg_sets = [[[_m_c_op], [], [_x_a_op]], [[_m_c_op], [_m_c_op], []], [[_m_c_op, _z_c_op], [_z_c_op], [_x_a_op]]]

δ = 0
ϵ = 0.5 * 2π
e_ops = pauli_vectors
H = δ * 0.5 * sigmax() + ϵ * 0.5 * sigmaz()
ψ0 = unit(2basis(2, 0) + basis(2, 1))
times = LinRange(0, 10, 100)

for (me_c_ops, brme_c_ops, brme_a_ops) in arg_sets
me = mesolve(H, ψ0, times, me_c_ops, e_ops = e_ops, progress_bar = Val(false))
brme = brmesolve(H, ψ0, times, brme_a_ops, brme_c_ops, e_ops = e_ops, progress_bar = Val(false))

@test all(me.expect .== brme.expect)
end
end;
Loading