Skip to content
Closed
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 @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for single `AbstractQuantumObject` in `sc_ops` for faster specific method in `ssesolve` and `smesolve`. ([#408])
- Change save callbacks from `PresetTimeCallback` to `FunctionCallingCallback`. ([#410])
- Align `eigenstates` and `eigenenergies` to QuTiP. ([#411])
- Introduce `groundstate`. ([#412])

## [v0.27.0]
Release date: 2025-02-14
Expand Down Expand Up @@ -147,3 +148,4 @@ Release date: 2024-11-13
[#408]: https://github.com/qutip/QuantumToolbox.jl/issues/408
[#410]: https://github.com/qutip/QuantumToolbox.jl/issues/410
[#411]: https://github.com/qutip/QuantumToolbox.jl/issues/411
[#412]: https://github.com/qutip/QuantumToolbox.jl/issues/412
1 change: 1 addition & 0 deletions docs/src/resources/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ partial_transpose
EigsolveResult
eigenenergies
eigenstates
groundstate
LinearAlgebra.eigen
LinearAlgebra.eigvals
eigsolve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Here is a table that summarizes all the supported linear algebra functions and a

- [`eigenenergies`](@ref): return eigenenergies (eigenvalues)
- [`eigenstates`](@ref): return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors)
- [`groundstate`](@ref): return the ground state eigenvalue and corresponding eigenvector
- [`eigvals`](@ref): return eigenvalues
- [`eigen`](@ref): using dense eigen solver and return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors)
- [`eigsolve`](@ref): using sparse eigen solver and return [`EigsolveResult`](@ref) (contains eigenvalues and eigenvectors)
Expand Down
53 changes: 48 additions & 5 deletions src/qobj/eigsolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Eigen solvers and results for QuantumObject
=#

export EigsolveResult
export eigenenergies, eigenstates, eigsolve
export eigenenergies, eigenstates, groundstate, eigsolve
export eigsolve_al

@doc raw"""
Expand Down Expand Up @@ -87,13 +87,19 @@ function Base.getproperty(res::EigsolveResult, key::Symbol)
end
end

_eigenvector_type(::Type{OperatorQuantumObject}) = Ket
_eigenvector_type(::Type{SuperOperatorQuantumObject}) = OperatorKet

Base.iterate(res::EigsolveResult) = (res.values, Val(:vector_list))
Base.iterate(res::EigsolveResult{T1,T2,Nothing}, ::Val{:vector_list}) where {T1,T2} =
([res.vectors[:, k] for k in 1:length(res.values)], Val(:vectors))
Base.iterate(res::EigsolveResult{T1,T2,OperatorQuantumObject}, ::Val{:vector_list}) where {T1,T2} =
([QuantumObject(res.vectors[:, k], Ket, res.dimensions) for k in 1:length(res.values)], Val(:vectors))
Base.iterate(res::EigsolveResult{T1,T2,SuperOperatorQuantumObject}, ::Val{:vector_list}) where {T1,T2} =
([QuantumObject(res.vectors[:, k], OperatorKet, res.dimensions) for k in 1:length(res.values)], Val(:vectors))
Base.iterate(
res::EigsolveResult{T1,T2,OpType},
::Val{:vector_list},
) where {T1,T2,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = (
[QuantumObject(res.vectors[:, k], _eigenvector_type(OpType), res.dimensions) for k in 1:length(res.values)],
Val(:vectors),
)
Base.iterate(res::EigsolveResult, ::Val{:vectors}) = (res.vectors, Val(:done))
Base.iterate(res::EigsolveResult, ::Val{:done}) = nothing

Expand Down Expand Up @@ -538,3 +544,40 @@ function eigenstates(
return eigsolve(A; kwargs...)
end
end

@doc raw"""
groundstate(A::QuantumObject; safe::Bool=true, tol::Real=1e-8, kwargs...)

Calculate the ground state eigenvalue and corresponding eigenvector

# Arguments
- `A::QuantumObject`: the [`QuantumObject`](@ref) to solve ground state eigenvalue and eigenvector
- `safe::Bool`: if `true` check for degenerate ground state. Default to `true`.
- `tol::Real`: the tolerance. Default is `1e-8`.
- `kwargs`: Additional keyword arguments passed to the solver. If `sparse=true`, the keyword arguments are passed to [`eigsolve`](@ref), otherwise to [`eigen`](@ref).

# Returns
- `eigval::Number`: the ground state eigenvalue
- `eigvec::QuantumObject`: the ground state eigenvector
"""
function groundstate(
A::QuantumObject{OpType};
safe::Bool = true,
tol::Real = 1e-8,
kwargs...,
) where {OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}}
# TODO: support for sparse eigsolve
#if !sparse
result = eigen(A; kwargs...)
#else
# eigvals = safe ? 2 : 1 # number of eigenvalues to calculate
# result = eigsolve(A; eigvals = eigvals, tol = tol, kwargs...)
#end

# the tolarence should be less strick than the `tol` for the eigensolver
# so it's numerical errors are not seen as degenerate states.
evals = result.values
safe && (abs(evals[2] - evals[1]) < (10 * tol)) && @warn "Ground state may be degenerate."

return evals[1], QuantumObject(result.vectors[:, 1], _eigenvector_type(OpType), result.dimensions)
Comment on lines +563 to +582
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to use eigenstates instead of eigen. In this way we can use all the internal kwargs, including also the sparse one.

In the case of sparse, it automatically finds the largest eigenvalue by the nature of the Arnoldi algorithm. However, this can be fixed by defining a sigma argument, which performs the shift-inverse procedure.

Copy link
Member Author

@ytdHuang ytdHuang Feb 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is why I didn't use the eigenstates
Because this function is only for calculating ground state. We should assign some of the keyword arguments, make it not adjustable from the users.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What value should I set for sigma in eigsolve to make it search for the lowest eigenvalue first?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this function is only for calculating ground state. We should assign some of the keyword arguments, make it not adjustable from the users.

What do you mean exactly? If we use eigenstates(A; kwargs...) we can forward all the kwargs to that, even the sparse = ... (which by default is false, so that we fall in this case). If the user specify sparse=true, then he/she is supposed to be more advanced, and should know that the shift-inverse method applies here. By doing so, they should also specify a sigma shift value. We can even write a note on that.

What value should I set for sigma in eigsolve to make it search for the lowest eigenvalue first?

sigma should be a value smaller than the supposed ground state energy. Let say that you expect the ground state energy to me close to 0, then sigma=-0.1 is fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, in general, I should set sigma = -Inf ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No this won't work. This is problem dependent.

The user using sparse=true is supposed to be advanced to be able to set this value.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm
Ok
I would probably close this PR.

Seems like we cannot make it.

end
10 changes: 10 additions & 0 deletions test/core-test/eigenvalues_and_operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@
@test isapprox(vec2mat(vecs[1]).data * exp(-1im * angle(vecs[1][1])), vec2mat(vecs2[1]).data, atol = 1e-7)
@test isapprox(vec2mat(vecs[1]).data * exp(-1im * angle(vecs[1][1])), vec2mat(state3[1]).data, atol = 1e-5)

# ground state # TODO: support for sparse eigsolve
U = rand_unitary(5)
M = U * Qobj(diagm([1, 1, 2, 3, 4])) * U' # degenerate ground state
gval_1, gvec_1 = @test_logs (:warn,) groundstate(M)
# gval_2, gvec_2 = @test_logs (:warn,) groundstate(M, sparse = true)
@test gval_1 ≈ 1# ≈ gval_2
#@test isapprox(gvec_1, gvec_2, atol = 1e-6)

@testset "Type Inference (eigen)" begin
N = 5
a = kron(destroy(N), qeye(N))
Expand All @@ -112,6 +120,8 @@
@inferred eigenstates(H, sparse = false)
@inferred eigenstates(H, sparse = true)
@inferred eigenstates(L, sparse = true)
@inferred groundstate(H)#, sparse = false) # TODO: support for sparse eigsolve
#@inferred groundstate(H, sparse = true)
@inferred eigsolve_al(L, 1 \ (40 * κ), eigvals = 10)
end
end