11export spectrum, spectrum_correlation_fft
2- export SpectrumSolver, ExponentialSeries, PseudoInverse
2+ export SpectrumSolver, ExponentialSeries, PseudoInverse, Lanczos
33
44abstract type SpectrumSolver end
55
2626
2727PseudoInverse (; 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{
4458See also the following list for `SpectrumSolver` docstrings:
4559- [`ExponentialSeries`](@ref)
4660- [`PseudoInverse`](@ref)
61+ - [`Lanczos`](@ref)
4762"""
4863function spectrum (
4964 H:: QuantumObject{HOpType} ,
@@ -144,6 +159,142 @@ function _spectrum(
144159 return spec
145160end
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