Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased](https://github.com/qutip/QuantumToolbox.jl/tree/main)

- Introduce `Lanczos` solver for `spectrum`. ([#476])
- Add Bloch-Redfield master equation solver. ([#473])

## [v0.31.1]
Release date: 2025-05-16
Expand Down Expand Up @@ -230,4 +231,5 @@ Release date: 2024-11-13
[#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455
[#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456
[#460]: https://github.com/qutip/QuantumToolbox.jl/issues/460
[#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473
[#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476
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
196 changes: 196 additions & 0 deletions src/time_evolution/brmesolve.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
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
isempty(a_ops) || (R1 += mapreduce(x -> _brterm(rst, x[1], x[2], sec_cutoff, Val(false)), +, a_ops))

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L44-L45

Added lines #L44 - L45 were not covered by tests

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 49 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L47-L49

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

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L51

Added line #L51 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 81 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L81

Added line #L81 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 91 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L88-L91

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

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L93

Added line #L93 was not covered by tests
end
end

function _brterm(

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#L97

Added line #L97 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 104 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L104

Added line #L104 was not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L106-L107

Added lines #L106 - L107 were not covered by tests

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

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#L109-L110

Added lines #L109 - L110 were not covered by tests

A_mat = U' * a_op.data * U

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L112

Added line #L112 was not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L114-L115

Added lines #L114 - L115 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 121 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L117-L121

Added lines #L117 - L121 were not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L123-L124

Added lines #L123 - L124 were not covered by tests
end

out =

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#L127

Added line #L127 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 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

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

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L135-L137

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

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
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 176 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L176

Added line #L176 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 186 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L186

Added line #L186 was not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L188

Added line #L188 was not covered by tests
end

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

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

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L191-L193

Added lines #L191 - L193 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 195 in src/time_evolution/brmesolve.jl

View check run for this annotation

Codecov / codecov/patch

src/time_evolution/brmesolve.jl#L195

Added line #L195 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