Skip to content

Commit fd3241e

Browse files
authored
Merge branch 'qutip:main' into fa/resolve
2 parents a2aa46c + fbe80da commit fd3241e

File tree

9 files changed

+518
-2
lines changed

9 files changed

+518
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

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

10+
- Introduce `Lanczos` solver for `spectrum`. ([#476])
11+
- Add Bloch-Redfield master equation solver. ([#473])
12+
1013
## [v0.31.1]
1114
Release date: 2025-05-16
1215

@@ -228,3 +231,5 @@ Release date: 2024-11-13
228231
[#455]: https://github.com/qutip/QuantumToolbox.jl/issues/455
229232
[#456]: https://github.com/qutip/QuantumToolbox.jl/issues/456
230233
[#460]: https://github.com/qutip/QuantumToolbox.jl/issues/460
234+
[#473]: https://github.com/qutip/QuantumToolbox.jl/issues/473
235+
[#476]: https://github.com/qutip/QuantumToolbox.jl/issues/476

benchmarks/correlations_and_spectrum.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ function benchmark_correlations_and_spectrum!(SUITE)
1919

2020
PI_solver = PseudoInverse()
2121

22+
L_solver = Lanczos()
23+
2224
SUITE["Correlations and Spectrum"]["FFT Correlation"] =
2325
@benchmarkable _calculate_fft_spectrum($H, $t_l, $c_ops, $(a'), $a)
2426

@@ -28,5 +30,8 @@ function benchmark_correlations_and_spectrum!(SUITE)
2830
SUITE["Correlations and Spectrum"]["Spectrum"]["Pseudo Inverse"] =
2931
@benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a, solver = $PI_solver)
3032

33+
SUITE["Correlations and Spectrum"]["Spectrum"]["Lanczos"] =
34+
@benchmarkable spectrum($H, $ω_l, $c_ops, $(a'), $a, solver = $L_solver)
35+
3136
return nothing
3237
end

docs/src/resources/api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ smesolve
208208
dfd_mesolve
209209
liouvillian
210210
liouvillian_generalized
211+
bloch_redfield_tensor
212+
brterm
213+
brmesolve
211214
```
212215

213216
### [Steady State Solvers](@id doc-API:Steady-State-Solvers)
@@ -248,6 +251,7 @@ spectrum_correlation_fft
248251
spectrum
249252
ExponentialSeries
250253
PseudoInverse
254+
Lanczos
251255
```
252256

253257
## [Entropy and Metrics](@id doc-API:Entropy-and-Metrics)

src/QuantumToolbox.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ include("time_evolution/callback_helpers/mcsolve_callback_helpers.jl")
106106
include("time_evolution/callback_helpers/ssesolve_callback_helpers.jl")
107107
include("time_evolution/callback_helpers/smesolve_callback_helpers.jl")
108108
include("time_evolution/mesolve.jl")
109+
include("time_evolution/brmesolve.jl")
109110
include("time_evolution/lr_mesolve.jl")
110111
include("time_evolution/sesolve.jl")
111112
include("time_evolution/mcsolve.jl")
@@ -115,14 +116,14 @@ include("time_evolution/time_evolution_dynamical.jl")
115116

116117
# Others
117118
include("correlations.jl")
118-
include("spectrum.jl")
119119
include("wigner.jl")
120120
include("spin_lattice.jl")
121121
include("arnoldi.jl")
122122
include("entropy.jl")
123123
include("metrics.jl")
124124
include("negativity.jl")
125125
include("steadystate.jl")
126+
include("spectrum.jl")
126127
include("visualization.jl")
127128

128129
# deprecated functions

src/spectrum.jl

Lines changed: 152 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export spectrum, spectrum_correlation_fft
2-
export SpectrumSolver, ExponentialSeries, PseudoInverse
2+
export SpectrumSolver, ExponentialSeries, PseudoInverse, Lanczos
33

44
abstract type SpectrumSolver end
55

@@ -26,6 +26,20 @@ end
2626

2727
PseudoInverse(; alg::SciMLLinearSolveAlgorithm = KrylovJL_GMRES()) = PseudoInverse(alg)
2828

29+
@doc raw"""
30+
Lanczos(; tol = 1e-8, maxiter = 5000, verbose = 0)
31+
32+
A solver which solves [`spectrum`](@ref) by using a non-symmetric Lanczos variant of the algorithm in [Koch2011](https://www.cond-mat.de/events/correl11/manuscripts/koch.pdf).
33+
The nonsymmetric Lanczos algorithm is adapted from Algorithm 6.6 in [Saad2011](https://www-users.cse.umn.edu/~saad/eig_book_2ndEd.pdf).
34+
The running estimate is updated via a [Wallis-Euler recursion](https://en.wikipedia.org/wiki/Continued_fraction).
35+
"""
36+
Base.@kwdef struct Lanczos{T<:Real,SS<:Union{Nothing,<:SteadyStateSolver}} <: SpectrumSolver
37+
tol::T = 1e-8
38+
maxiter::Int = 5000
39+
verbose::Int = 0
40+
steadystate_solver::SS = nothing
41+
end
42+
2943
@doc raw"""
3044
spectrum(H::QuantumObject,
3145
ωlist::AbstractVector,
@@ -44,6 +58,7 @@ S(\omega) = \int_{-\infty}^\infty \lim_{t \rightarrow \infty} \left\langle \hat{
4458
See also the following list for `SpectrumSolver` docstrings:
4559
- [`ExponentialSeries`](@ref)
4660
- [`PseudoInverse`](@ref)
61+
- [`Lanczos`](@ref)
4762
"""
4863
function spectrum(
4964
H::QuantumObject{HOpType},
@@ -144,6 +159,142 @@ function _spectrum(
144159
return spec
145160
end
146161

162+
function _spectrum(
163+
L::QuantumObject{SuperOperator},
164+
ωlist::AbstractVector,
165+
A::QuantumObject{Operator},
166+
B::QuantumObject{Operator},
167+
solver::Lanczos,
168+
)
169+
check_dimensions(L, A, B)
170+
171+
# Define type shortcuts
172+
fT = _FType(L)
173+
cT = _CType(L)
174+
175+
# Calculate |v₁> = B|ρss>
176+
ρss =
177+
isnothing(solver.steadystate_solver) ? mat2vec(steadystate(L)) :
178+
mat2vec(steadystate(L; solver = solver.steadystate_solver))
179+
vₖ = (spre(B) * ρss).data
180+
181+
# Define (possibly GPU) vector type
182+
vT = typeof(vₖ)
183+
184+
# Calculate <w₁| = <I|A
185+
D = prod(L.dimensions)
186+
Ivec = SparseVector(D^2, [1 + n * (D + 1) for n in 0:(D-1)], ones(cT, D)) # same as vec(system_identity_matrix)
187+
wₖ = spre(A).data' * vT(Ivec)
188+
189+
# Store the norm of the Green's function before renormalizing |v₁> and <w₁|
190+
gfNorm = abs(dot(wₖ, vₖ))
191+
vₖ ./= sqrt(gfNorm)
192+
wₖ ./= sqrt(gfNorm)
193+
194+
# Handle input frequency range
195+
ωList = vT(convert(Vector{fT}, ωlist)) # Make sure they're real frequencies and potentially on GPU
196+
Length = length(ωList)
197+
198+
# Current and previous estimates of the spectrum
199+
lanczosFactor = vT(zeros(cT, Length))
200+
lanczosFactor₋₁ = vT(zeros(cT, Length))
201+
202+
# Tridiagonal matrix elements
203+
αₖ = cT(0)
204+
βₖ = cT(-1)
205+
δₖ = fT(+1)
206+
207+
# Current and up to second-to-last A and B Euler sequences
208+
A₋₂ = vT(ones(cT, Length))
209+
A₋₁ = vT(zeros(cT, Length))
210+
Aₖ = vT(zeros(cT, Length))
211+
B₋₂ = vT(zeros(cT, Length))
212+
B₋₁ = vT(ones(cT, Length))
213+
Bₖ = vT(zeros(cT, Length))
214+
215+
# Maximum norm and residue
216+
maxNorm = vT(zeros(cT, length(ωList)))
217+
maxResidue = fT(0.0)
218+
219+
# Previous and next left/right Krylov vectors
220+
v₋₁ = vT(zeros(cT, D^2))
221+
v₊₁ = vT(zeros(cT, D^2))
222+
w₋₁ = vT(zeros(cT, D^2))
223+
w₊₁ = vT(zeros(cT, D^2))
224+
225+
# Frequency of renormalization
226+
renormFrequency = 1
227+
228+
# Loop over the Krylov subspace(s)
229+
for k in 1:solver.maxiter
230+
# k-th diagonal element
231+
mul!(w₊₁, L.data', wₖ)
232+
αₖ = dot(w₊₁, vₖ)
233+
234+
# Update A(k), B(k) and continuous fraction; normalization avoids overflow
235+
Aₖ .= (-1im .* ωList .+ αₖ) .* A₋₁ .- (βₖ * δₖ) .* A₋₂
236+
Bₖ .= (-1im .* ωList .+ αₖ) .* B₋₁ .- (βₖ * δₖ) .* B₋₂
237+
lanczosFactor₋₁ .= lanczosFactor
238+
lanczosFactor .= Aₖ ./ Bₖ
239+
240+
# Renormalize Euler sequences to avoid overflow
241+
if k % renormFrequency == 0
242+
map!((x, y) -> max(abs(x), abs(y)), maxNorm, Aₖ, Bₖ)
243+
Aₖ ./= maxNorm
244+
Bₖ ./= maxNorm
245+
A₋₁ ./= maxNorm
246+
B₋₁ ./= maxNorm
247+
end
248+
249+
# Check for convergence
250+
251+
residueNorm = max(maximum(abs, lanczosFactor), maximum(abs, lanczosFactor₋₁))
252+
lanczosFactor₋₁ .-= lanczosFactor
253+
maxResidue = maximum(abs, lanczosFactor₋₁) / residueNorm
254+
if maxResidue <= solver.tol
255+
if solver.verbose > 1
256+
println("spectrum(): solver::Lanczos converged after $(k) iterations")
257+
end
258+
break
259+
end
260+
261+
# (k+1)-th left/right vectors, orthogonal to previous ones
262+
mul!(v₊₁, L.data, vₖ)
263+
v₊₁ .= v₊₁ .- αₖ .* vₖ .- βₖ .* v₋₁
264+
w₊₁ .= w₊₁ .- conj(αₖ) .* wₖ .- conj(δₖ) .* w₋₁
265+
v₋₁, vₖ = vₖ, v₋₁
266+
vₖ, v₊₁ = v₊₁, vₖ
267+
w₋₁, wₖ = wₖ, w₋₁
268+
wₖ, w₊₁ = w₊₁, wₖ
269+
270+
# k-th off-diagonal elements
271+
buf = dot(wₖ, vₖ)
272+
δₖ = sqrt(abs(buf))
273+
βₖ = buf / δₖ
274+
275+
# Normalize (k+1)-th left/right vectors
276+
vₖ ./= δₖ
277+
wₖ ./= conj(βₖ)
278+
279+
# Update everything for the next cycle
280+
A₋₂, A₋₁ = A₋₁, A₋₂
281+
A₋₁, Aₖ = Aₖ, A₋₁
282+
B₋₂, B₋₁ = B₋₁, B₋₂
283+
B₋₁, Bₖ = Bₖ, B₋₁
284+
end
285+
286+
if solver.verbose > 0 && maxResidue > solver.tol
287+
println("spectrum(): maxiter = $(solver.maxiter) reached before convergence!")
288+
println("spectrum(): Max residue = $maxResidue")
289+
println("spectrum(): Consider increasing maxiter and/or tol")
290+
end
291+
292+
# Restore the norm
293+
lanczosFactor .= gfNorm .* lanczosFactor
294+
295+
return -2 .* real(lanczosFactor)
296+
end
297+
147298
@doc raw"""
148299
spectrum_correlation_fft(tlist, corr; inverse=false)
149300

0 commit comments

Comments
 (0)