Skip to content

Commit 252e362

Browse files
Add sortby and rev arguments to eigensolver functions (#546)
1 parent c986d84 commit 252e362

File tree

3 files changed

+78
-30
lines changed

3 files changed

+78
-30
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Release date: 2025-08-23
2121

2222
- Improve Bloch sphere rendering for animation. ([#520])
2323
- Add support to `Enzyme.jl` for `sesolve` and `mesolve`. ([#531])
24+
- Add `sortby` and `rev` keyword arguments to eigensolvers. ([#546])
2425

2526
## [v0.34.0]
2627
Release date: 2025-07-29
@@ -314,3 +315,4 @@ Release date: 2024-11-13
314315
[#536]: https://github.com/qutip/QuantumToolbox.jl/issues/536
315316
[#537]: https://github.com/qutip/QuantumToolbox.jl/issues/537
316317
[#539]: https://github.com/qutip/QuantumToolbox.jl/issues/539
318+
[#546]: https://github.com/qutip/QuantumToolbox.jl/issues/546

src/qobj/eigsolve.jl

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,12 @@ function _permuteschur!(
149149
return T, Q
150150
end
151151

152-
function _update_schur_eigs!(Hₘ, Uₘ, Uₘᵥ, f, m, β, sorted_vals)
152+
function _update_schur_eigs!(Hₘ, Uₘ, Uₘᵥ, f, m, β, sorted_vals, sortby, rev)
153153
F = hessenberg!(Hₘ)
154154
copyto!(Uₘ, Hₘ)
155155
LAPACK.orghr!(1, m, Uₘ, F.τ)
156156
Tₘ, Uₘ, values = hseqr!(Hₘ, Uₘ)
157-
sortperm!(sorted_vals, values, by = abs, rev = true)
157+
sortperm!(sorted_vals, values, by = sortby, rev = rev)
158158
_permuteschur!(Tₘ, Uₘ, sorted_vals)
159159
mul!(f, Uₘᵥ, β)
160160

@@ -170,6 +170,8 @@ function _eigsolve(
170170
m::Int = max(20, 2 * k + 1);
171171
tol::Real = 1e-8,
172172
maxiter::Int = 200,
173+
sortby::Function = abs2,
174+
rev = true,
173175
) where {T<:BlasFloat,ObjType<:Union{Nothing,Operator,SuperOperator}}
174176
n = size(A, 2)
175177
V = similar(b, n, m + 1)
@@ -209,7 +211,7 @@ function _eigsolve(
209211

210212
M = typeof(cache0)
211213

212-
Tₘ, Uₘ = _update_schur_eigs!(Hₘ, Uₘ, Uₘᵥ, f, m, β, sorted_vals)
214+
Tₘ, Uₘ = _update_schur_eigs!(Hₘ, Uₘ, Uₘᵥ, f, m, β, sorted_vals, sortby, rev)
213215

214216
numops = m
215217
iter = 0
@@ -235,7 +237,7 @@ function _eigsolve(
235237

236238
# println( A * Vₘ ≈ Vₘ * M(Hₘ) + qₘ * M(transpose(βeₘ)) ) # SHOULD BE TRUE
237239

238-
Tₘ, Uₘ = _update_schur_eigs!(Hₘ, Uₘ, Uₘᵥ, f, m, β, sorted_vals)
240+
Tₘ, Uₘ = _update_schur_eigs!(Hₘ, Uₘ, Uₘᵥ, f, m, β, sorted_vals, sortby, rev)
239241

240242
numops += m - k - 1
241243
iter += 1
@@ -263,6 +265,8 @@ end
263265
tol::Real = 1e-8,
264266
maxiter::Int = 200,
265267
solver::Union{Nothing, SciMLLinearSolveAlgorithm} = nothing,
268+
sortby::Function = abs2,
269+
rev::Bool = true,
266270
kwargs...)
267271
268272
Solve for the eigenvalues and eigenvectors of a matrix `A` using the Arnoldi method.
@@ -276,6 +280,8 @@ Solve for the eigenvalues and eigenvectors of a matrix `A` using the Arnoldi met
276280
- `tol::Real`: the tolerance for the Arnoldi method. Default is `1e-8`.
277281
- `maxiter::Int`: the maximum number of iterations for the Arnoldi method. Default is `200`.
278282
- `solver::Union{Nothing, SciMLLinearSolveAlgorithm}`: the linear solver algorithm. Default is `nothing`.
283+
- `sortby::Function`: the function to sort eigenvalues. Default is `abs2`.
284+
- `rev::Bool`: whether to sort in descending order. Default is `true`.
279285
- `kwargs`: Additional keyword arguments passed to the solver.
280286
281287
# Notes
@@ -293,6 +299,8 @@ function eigsolve(
293299
tol::Real = 1e-8,
294300
maxiter::Int = 200,
295301
solver::Union{Nothing,SciMLLinearSolveAlgorithm} = nothing,
302+
sortby::Function = abs2,
303+
rev::Bool = true,
296304
kwargs...,
297305
)
298306
return eigsolve(
@@ -306,6 +314,8 @@ function eigsolve(
306314
tol = tol,
307315
maxiter = maxiter,
308316
solver = solver,
317+
sortby = sortby,
318+
rev = rev,
309319
kwargs...,
310320
)
311321
end
@@ -321,14 +331,27 @@ function eigsolve(
321331
tol::Real = 1e-8,
322332
maxiter::Int = 200,
323333
solver::Union{Nothing,SciMLLinearSolveAlgorithm} = nothing,
334+
sortby::Function = abs2,
335+
rev::Bool = true,
324336
kwargs...,
325337
)
326338
T = eltype(A)
327339
isH = ishermitian(A)
328340
v0 === nothing && (v0 = normalize!(rand(T, size(A, 1))))
329341

330342
if sigma === nothing
331-
res = _eigsolve(A, v0, type, dimensions, eigvals, krylovdim, tol = tol, maxiter = maxiter)
343+
res = _eigsolve(
344+
A,
345+
v0,
346+
type,
347+
dimensions,
348+
eigvals,
349+
krylovdim,
350+
tol = tol,
351+
maxiter = maxiter,
352+
sortby = sortby,
353+
rev = rev,
354+
)
332355
vals = res.values
333356
else
334357
Aₛ = A - sigma * I
@@ -346,7 +369,18 @@ function eigsolve(
346369

347370
Amap = EigsolveInverseMap(T, size(A), linsolve)
348371

349-
res = _eigsolve(Amap, v0, type, dimensions, eigvals, krylovdim, tol = tol, maxiter = maxiter)
372+
res = _eigsolve(
373+
Amap,
374+
v0,
375+
type,
376+
dimensions,
377+
eigvals,
378+
krylovdim,
379+
tol = tol,
380+
maxiter = maxiter,
381+
sortby = sortby,
382+
rev = rev,
383+
)
350384
vals = @. (1 + sigma * res.values) / res.values
351385
end
352386

@@ -368,6 +402,8 @@ end
368402
krylovdim::Int = min(10, size(H, 1)),
369403
maxiter::Int = 200,
370404
eigstol::Real = 1e-6,
405+
sortby::Function = abs2,
406+
rev::Bool = true,
371407
kwargs...,
372408
)
373409
@@ -384,6 +420,8 @@ Solve the eigenvalue problem for a Liouvillian superoperator `L` using the Arnol
384420
- `krylovdim`: The dimension of the Krylov subspace.
385421
- `maxiter`: The maximum number of iterations for the eigsolver.
386422
- `eigstol`: The tolerance for the eigsolver.
423+
- `sortby::Function`: the function to sort eigenvalues. Default is `abs2`.
424+
- `rev::Bool`: whether to sort in descending order. Default is `true`.
387425
- `kwargs`: Additional keyword arguments passed to the differential equation solver.
388426
389427
# Notes
@@ -407,6 +445,8 @@ function eigsolve_al(
407445
krylovdim::Int = min(10, size(H, 1)),
408446
maxiter::Int = 200,
409447
eigstol::Real = 1e-6,
448+
sortby::Function = abs2,
449+
rev::Bool = true,
410450
kwargs...,
411451
) where {HOpType<:Union{Operator,SuperOperator}}
412452
L_evo = _mesolve_make_L_QobjEvo(H, c_ops)
@@ -422,8 +462,18 @@ function eigsolve_al(
422462

423463
Lmap = ArnoldiLindbladIntegratorMap(eltype(H), size(L_evo), integrator)
424464

425-
res =
426-
_eigsolve(Lmap, mat2vec(ρ0), L_evo.type, L_evo.dimensions, eigvals, krylovdim, maxiter = maxiter, tol = eigstol)
465+
res = _eigsolve(
466+
Lmap,
467+
mat2vec(ρ0),
468+
L_evo.type,
469+
L_evo.dimensions,
470+
eigvals,
471+
krylovdim,
472+
maxiter = maxiter,
473+
tol = eigstol,
474+
sortby = sortby,
475+
rev = rev,
476+
)
427477

428478
vals = similar(res.values)
429479
vecs = similar(res.vectors)

test/core-test/eigenvalues_and_operators.jl

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
@testitem "Eigenvalues and Operators" begin
1+
@testitem "Eigenvalues" begin
22
σx = sigmax()
33
result = eigenstates(σx, sparse = false)
44
λd, ψd, Td = result
@@ -33,12 +33,10 @@
3333

3434
vals_d, vecs_d, mat_d = eigenstates(H_d)
3535
vals_c, vecs_c, mat_c = eigenstates(H_c)
36-
vals2, vecs2, mat2 = eigenstates(H_d, sparse = true, sigma = -0.9, eigvals = 10, krylovdim = 30)
37-
sort!(vals_c, by = real)
38-
sort!(vals2, by = real)
36+
vals2, vecs2, mat2 = eigenstates(H_d, sparse = true, sigma = -0.9, eigvals = 10, krylovdim = 30, by = real)
3937

40-
@test sum(real.(vals_d[1:20]) .- real.(vals_c[1:20])) / 20 < 1e-3
41-
@test sum(real.(vals_d[1:10]) .- real.(vals2[1:10])) / 20 < 1e-3
38+
@test real.(vals_d[1:20]) real.(vals_c[1:20])
39+
@test real.(vals_d[1:10]) real.(vals2[1:10])
4240

4341
N = 5
4442
a = kron(destroy(N), qeye(N))
@@ -58,16 +56,15 @@
5856

5957
# eigen solve for general matrices
6058
vals, _, vecs = eigsolve(L.data, sigma = 0.01, eigvals = 10, krylovdim = 50)
61-
vals2, _, vecs2 = eigenstates(L)
59+
vals2, _, vecs2 = eigenstates(L; sortby = abs)
6260
vals3, state3, vecs3 = eigsolve_al(L, 1 \ (40 * κ), eigvals = 10, krylovdim = 50)
63-
idxs = sortperm(vals2, by = abs)
64-
vals2 = vals2[idxs][1:10]
65-
vecs2 = vecs2[:, idxs][:, 1:10]
61+
vals2 = vals2[1:10]
62+
vecs2 = vecs2[:, 1:10]
6663

67-
@test isapprox(sum(abs2, vals), sum(abs2, vals2), atol = 1e-7)
68-
@test isapprox(abs2(vals2[1]), abs2(vals3[1]), atol = 1e-7)
69-
@test isapprox(vec2mat(vecs[:, 1]) * exp(-1im * angle(vecs[1, 1])), vec2mat(vecs2[:, 1]), atol = 1e-7)
70-
@test isapprox(vec2mat(vecs[:, 1]) * exp(-1im * angle(vecs[1, 1])), vec2mat(vecs3[:, 1]), atol = 1e-5)
64+
@test sum(abs2, vals) sum(abs2, vals2)
65+
@test abs2(vals2[1]) abs2(vals3[1]) atol=1e-7
66+
@test vec2mat(vecs[:, 1]) * exp(-1im * angle(vecs[1, 1])) vec2mat(vecs2[:, 1]) atol=1e-7
67+
@test vec2mat(vecs[:, 1]) * exp(-1im * angle(vecs[1, 1])) vec2mat(vecs3[:, 1]) atol=1e-5
7168

7269
# eigen solve for QuantumObject
7370
result = eigenstates(L, sparse = true, sigma = 0.01, eigvals = 10, krylovdim = 50)
@@ -78,19 +75,18 @@
7875
@test resstring ==
7976
"EigsolveResult: type=$(SuperOperator()) dims=$(result.dims)\nvalues:\n$(valstring)\nvectors:\n$vecsstring"
8077

81-
vals2, vecs2 = eigenstates(L, sparse = false)
82-
idxs = sortperm(vals2, by = abs)
83-
vals2 = vals2[idxs][1:10]
84-
vecs2 = vecs2[idxs][1:10]
78+
vals2, vecs2 = eigenstates(L, sortby = abs)
79+
vals2 = vals2[1:10]
80+
vecs2 = vecs2[1:10]
8581

8682
@test result.type isa SuperOperator
8783
@test result.dims == L.dims
8884
@test all([v.type isa OperatorKet for v in vecs])
8985
@test typeof(result.vectors) <: AbstractMatrix
90-
@test isapprox(sum(abs2, vals), sum(abs2, vals2), atol = 1e-7)
91-
@test isapprox(abs2(vals2[1]), abs2(vals3[1]), atol = 1e-7)
92-
@test isapprox(vec2mat(vecs[1]).data * exp(-1im * angle(vecs[1][1])), vec2mat(vecs2[1]).data, atol = 1e-7)
93-
@test isapprox(vec2mat(vecs[1]).data * exp(-1im * angle(vecs[1][1])), vec2mat(state3[1]).data, atol = 1e-5)
86+
@test sum(abs2, vals) sum(abs2, vals2)
87+
@test abs2(vals2[1]) abs2(vals3[1]) atol=1e-7
88+
@test vec2mat(vecs[1]).data * exp(-1im * angle(vecs[1][1])) vec2mat(vecs2[1]).data
89+
@test vec2mat(vecs[1]).data * exp(-1im * angle(vecs[1][1])) vec2mat(state3[1]).data atol=1e-5
9490

9591
@testset "Type Inference (eigen)" begin
9692
N = 5

0 commit comments

Comments
 (0)