Skip to content

Commit 6add50d

Browse files
Fix type instabilities for almost all functions (qutip#221)
* Improve c_ops handling * Format code * Fix ptrace and operators * Make states stable * Fix type instabilities for qobj methods * FIx eigenvalues * Other fixes and format * Minor changes to dfd_mesolve * Fix typo * Remove version condition of runtest
1 parent d736766 commit 6add50d

25 files changed

+641
-170
lines changed

Project.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
1111
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
1212
IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895"
1313
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
14-
LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e"
1514
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
1615
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
1716
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
@@ -39,7 +38,6 @@ FFTW = "1.5"
3938
Graphs = "1.7"
4039
IncompleteLU = "0.2"
4140
LinearAlgebra = "<0.0.1, 1"
42-
LinearMaps = "3"
4341
LinearSolve = "2"
4442
OrdinaryDiffEqCore = "1"
4543
OrdinaryDiffEqTsit5 = "1"

docs/src/api.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@ wigner
219219
negativity
220220
```
221221

222+
## [Linear Maps](@id doc-API:Linear-Maps)
223+
224+
```@docs
225+
AbstractLinearMap
226+
```
227+
222228
## [Utility functions](@id doc-API:Utility-functions)
223229

224230
```@docs

docs/src/users_guide/QuantumObject/QuantumObject.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ Qobj(M, dims = (2, 2)) # dims as Tuple (recommended)
4545
Qobj(M, dims = SVector(2, 2)) # dims as StaticArrays.SVector (recommended)
4646
```
4747

48-
> [!IMPORTANT]
49-
> Please note that here we put the `dims` as a tuple `(2, 2)`. Although it supports also `Vector` type (`dims = [2, 2]`), it is recommended to use `Tuple` or `SVector` from [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) to improve performance. For a brief explanation on the impact of the type of `dims`, see the Section [The Importance of Type-Stability](@ref doc:Type-Stability).
48+
!!! warning "Beware of type-stability!"
49+
Please note that here we put the `dims` as a tuple `(2, 2)`. Although it supports also `Vector` type (`dims = [2, 2]`), it is recommended to use `Tuple` or `SVector` from [`StaticArrays.jl`](https://github.com/JuliaArrays/StaticArrays.jl) to improve performance. For a brief explanation on the impact of the type of `dims`, see the Section [The Importance of Type-Stability](@ref doc:Type-Stability).
5050

5151
```@example Qobj
5252
Qobj(rand(4, 4), type = SuperOperator)

src/QuantumToolbox.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import ArrayInterface: allowed_getindex, allowed_setindex!
4040
import FFTW: fft, fftshift
4141
import Graphs: connected_components, DiGraph
4242
import IncompleteLU: ilu
43-
import LinearMaps: LinearMap
4443
import Pkg
4544
import Random
4645
import SpecialFunctions: loggamma
@@ -54,6 +53,7 @@ BLAS.set_num_threads(1)
5453
include("utilities.jl")
5554
include("versioninfo.jl")
5655
include("progress_bar.jl")
56+
include("linear_maps.jl")
5757

5858
# Quantum Object
5959
include("qobj/quantum_object.jl")

src/linear_maps.jl

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
export AbstractLinearMap
2+
3+
@doc raw"""
4+
AbstractLinearMap{T, TS}
5+
6+
Represents a general linear map with element type `T` and size `TS`.
7+
8+
## Overview
9+
10+
A **linear map** is a transformation `L` that satisfies:
11+
12+
- **Additivity**:
13+
```math
14+
L(u + v) = L(u) + L(v)
15+
```
16+
- **Homogeneity**:
17+
```math
18+
L(cu) = cL(u)
19+
```
20+
21+
It is typically represented as a matrix with dimensions given by `size`, and this abtract type helps to define this map when the matrix is not explicitly available.
22+
23+
## Methods
24+
25+
- `Base.eltype(A)`: Returns the element type `T`.
26+
- `Base.size(A)`: Returns the size `A.size`.
27+
- `Base.size(A, i)`: Returns the `i`-th dimension.
28+
29+
## Example
30+
31+
As an example, we now define the linear map used in the [`eigsolve_al`](@ref) function for Arnoldi-Lindblad eigenvalue solver:
32+
33+
```julia-repl
34+
struct ArnoldiLindbladIntegratorMap{T,TS,TI} <: AbstractLinearMap{T,TS}
35+
elty::Type{T}
36+
size::TS
37+
integrator::TI
38+
end
39+
40+
function LinearAlgebra.mul!(y::AbstractVector, A::ArnoldiLindbladIntegratorMap, x::AbstractVector)
41+
reinit!(A.integrator, x)
42+
solve!(A.integrator)
43+
return copyto!(y, A.integrator.u)
44+
end
45+
```
46+
47+
where `integrator` is the ODE integrator for the time-evolution. In this way, we can diagonalize this linear map using the [`eigsolve`](@ref) function.
48+
"""
49+
abstract type AbstractLinearMap{T,TS} end
50+
51+
Base.eltype(A::AbstractLinearMap{T}) where {T} = T
52+
53+
Base.size(A::AbstractLinearMap) = A.size
54+
Base.size(A::AbstractLinearMap, i::Int) = A.size[i]

src/metrics.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,12 @@ function entropy_vn(
5353
base::Int = 0,
5454
tol::Real = 1e-15,
5555
) where {T}
56-
vals = eigvals(ρ)
57-
indexes = abs.(vals) .> tol
58-
1 indexes && return 0
56+
vals = eigenenergies(ρ)
57+
indexes = findall(x -> abs(x) > tol, vals)
58+
length(indexes) == 0 && return zero(real(T))
5959
nzvals = vals[indexes]
6060
logvals = base != 0 ? log.(base, Complex.(nzvals)) : log.(Complex.(nzvals))
61-
return -real(sum(nzvals .* logvals))
61+
return -real(mapreduce(*, +, nzvals, logvals))
6262
end
6363

6464
"""

src/qobj/arithmetic_and_attributes.jl

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,10 @@ julia> tr(a' * a)
239239
"""
240240
LinearAlgebra.tr(
241241
A::QuantumObject{<:AbstractArray{T},OpType},
242-
) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} =
243-
ishermitian(A) ? real(tr(A.data)) : tr(A.data)
242+
) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = tr(A.data)
243+
LinearAlgebra.tr(
244+
A::QuantumObject{<:Union{<:Hermitian{TF},Symmetric{TR}},OpType},
245+
) where {TF<:BlasFloat,TR<:Real,OpType<:OperatorQuantumObject} = real(tr(A.data))
244246

245247
@doc raw"""
246248
svdvals(A::QuantumObject)
@@ -515,7 +517,7 @@ function ptrace(QO::QuantumObject{<:AbstractArray,KetQuantumObject}, sel::Union{
515517
length(QO.dims) == 1 && return QO
516518

517519
ρtr, dkeep = _ptrace_ket(QO.data, QO.dims, SVector(sel))
518-
return QuantumObject(ρtr, dims = dkeep)
520+
return QuantumObject(ρtr, type = Operator, dims = dkeep)
519521
end
520522

521523
ptrace(QO::QuantumObject{<:AbstractArray,BraQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel)
@@ -524,7 +526,7 @@ function ptrace(QO::QuantumObject{<:AbstractArray,OperatorQuantumObject}, sel::U
524526
length(QO.dims) == 1 && return QO
525527

526528
ρtr, dkeep = _ptrace_oper(QO.data, QO.dims, SVector(sel))
527-
return QuantumObject(ρtr, dims = dkeep)
529+
return QuantumObject(ρtr, type = Operator, dims = dkeep)
528530
end
529531
ptrace(QO::QuantumObject, sel::Int) = ptrace(QO, SVector(sel))
530532

@@ -547,7 +549,7 @@ function _ptrace_ket(QO::AbstractArray, dims::Union{SVector,MVector}, sel)
547549

548550
vmat = reshape(QO, reverse(dims)...)
549551
topermute = nd + 1 .- sel_qtrace
550-
vmat = PermutedDimsArray(vmat, topermute)
552+
vmat = permutedims(vmat, topermute) # TODO: use PermutedDimsArray when Julia v1.11.0 is released
551553
vmat = reshape(vmat, prod(dkeep), prod(dtrace))
552554

553555
return vmat * vmat', dkeep
@@ -576,14 +578,14 @@ function _ptrace_oper(QO::AbstractArray, dims::Union{SVector,MVector}, sel)
576578

577579
ρmat = reshape(QO, reverse(vcat(dims, dims))...)
578580
topermute = 2 * nd + 1 .- qtrace_sel
579-
ρmat = PermutedDimsArray(ρmat, reverse(topermute))
581+
ρmat = permutedims(ρmat, reverse(topermute)) # TODO: use PermutedDimsArray when Julia v1.11.0 is released
580582

581583
## TODO: Check if it works always
582584

583585
# ρmat = row_major_reshape(ρmat, prod(dtrace), prod(dtrace), prod(dkeep), prod(dkeep))
584586
# res = dropdims(mapslices(tr, ρmat, dims=(1,2)), dims=(1,2))
585587
ρmat = reshape(ρmat, prod(dkeep), prod(dkeep), prod(dtrace), prod(dtrace))
586-
res = dropdims(mapslices(tr, ρmat, dims = (3, 4)), dims = (3, 4))
588+
res = map(tr, eachslice(ρmat, dims = (1, 2)))
587589

588590
return res, dkeep
589591
end
@@ -673,6 +675,9 @@ julia> ψ_123 = tensor(ψ1, ψ2, ψ3)
673675
julia> permute(ψ_123, [2, 1, 3]) ≈ tensor(ψ2, ψ1, ψ3)
674676
true
675677
```
678+
679+
!!! warning "Beware of type-stability!"
680+
It is highly recommended to use `permute(A, order)` with `order` as `Tuple` or `SVector` to keep type stability. See the [related Section](@ref doc:Type-Stability) about type stability for more details.
676681
"""
677682
function permute(
678683
A::QuantumObject{<:AbstractArray{T},ObjType},
@@ -691,7 +696,7 @@ function permute(
691696
dims, perm = _dims_and_perm(A.type, A.dims, order_svector, length(order_svector))
692697

693698
return QuantumObject(
694-
reshape(PermutedDimsArray(reshape(A.data, dims...), Tuple(perm)), size(A)),
699+
reshape(permutedims(reshape(A.data, dims...), Tuple(perm)), size(A)),
695700
A.type,
696701
A.dims[order_svector],
697702
)

src/qobj/eigsolve.jl

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,27 @@ function Base.show(io::IO, res::EigsolveResult)
8484
return show(io, MIME("text/plain"), res.vectors)
8585
end
8686

87-
function _map_ldiv(linsolve, y, x)
88-
linsolve.b .= x
89-
return y .= solve!(linsolve).u
87+
struct ArnoldiLindbladIntegratorMap{T,TS,TI} <: AbstractLinearMap{T,TS}
88+
elty::Type{T}
89+
size::TS
90+
integrator::TI
91+
end
92+
93+
function LinearAlgebra.mul!(y::AbstractVector, A::ArnoldiLindbladIntegratorMap, x::AbstractVector)
94+
reinit!(A.integrator, x)
95+
solve!(A.integrator)
96+
return copyto!(y, A.integrator.u)
97+
end
98+
99+
struct EigsolveInverseMap{T,TS,TI} <: AbstractLinearMap{T,TS}
100+
elty::Type{T}
101+
size::TS
102+
linsolve::TI
103+
end
104+
105+
function LinearAlgebra.mul!(y::AbstractVector, A::EigsolveInverseMap, x::AbstractVector)
106+
A.linsolve.b .= x
107+
return copyto!(y, solve!(A.linsolve).u)
90108
end
91109

92110
function _permuteschur!(
@@ -293,7 +311,8 @@ function eigsolve(
293311

294312
prob = LinearProblem(Aₛ, v0)
295313
linsolve = init(prob, solver; kwargs2...)
296-
Amap = LinearMap{T}((y, x) -> _map_ldiv(linsolve, y, x), length(v0))
314+
315+
Amap = EigsolveInverseMap(T, size(A), linsolve)
297316

298317
res = _eigsolve(Amap, v0, type, dims, k, krylovdim, tol = tol, maxiter = maxiter)
299318
vals = @. (1 + sigma * res.values) / res.values
@@ -370,13 +389,7 @@ function eigsolve_al(
370389

371390
# prog = ProgressUnknown(desc="Applications:", showspeed = true, enabled=progress)
372391

373-
function arnoldi_lindblad_solve(ρ)
374-
reinit!(integrator, ρ)
375-
solve!(integrator)
376-
return integrator.u
377-
end
378-
379-
Lmap = LinearMap{eltype(MT1)}(arnoldi_lindblad_solve, size(L, 1), ismutating = false)
392+
Lmap = ArnoldiLindbladIntegratorMap(eltype(MT1), size(L), integrator)
380393

381394
res = _eigsolve(Lmap, mat2vec(ρ0), L.type, L.dims, k, krylovdim, maxiter = maxiter, tol = eigstol)
382395
# finish!(prog)

src/qobj/functions.jl

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ If `ψ` is a [`Ket`](@ref) or [`Bra`](@ref), the function calculates ``\langle\p
2525
2626
If `ψ` is a density matrix ([`Operator`](@ref)), the function calculates ``\textrm{Tr} \left[ \hat{O} \hat{\psi} \right]``
2727
28-
The function returns a real number if `O` is hermitian, and returns a complex number otherwise.
28+
The function returns a real number if `O` is of `Hermitian` type or `Symmetric` type, and returns a complex number otherwise. You can make an operator `O` hermitian by using `Hermitian(O)`.
2929
3030
# Examples
3131
@@ -34,31 +34,48 @@ julia> ψ = 1 / √2 * (fock(10,2) + fock(10,4));
3434
3535
julia> a = destroy(10);
3636
37-
julia> expect(a' * a, ψ) ≈ 3
38-
true
37+
julia> expect(a' * a, ψ) |> round
38+
3.0 + 0.0im
39+
40+
julia> expect(Hermitian(a' * a), ψ) |> round
41+
3.0
3942
```
4043
"""
4144
function expect(
4245
O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject},
4346
ψ::QuantumObject{<:AbstractArray{T2},KetQuantumObject},
4447
) where {T1,T2}
45-
ψd = ψ.data
46-
Od = O.data
47-
return ishermitian(O) ? real(dot(ψd, Od, ψd)) : dot(ψd, Od, ψd)
48+
return dot.data, O.data, ψ.data)
4849
end
4950
function expect(
5051
O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject},
5152
ψ::QuantumObject{<:AbstractArray{T2},BraQuantumObject},
5253
) where {T1,T2}
53-
ψd = ψ.data'
54-
Od = O.data
55-
return ishermitian(O) ? real(dot(ψd, Od, ψd)) : dot(ψd, Od, ψd)
54+
return expect(O, ψ')
5655
end
5756
function expect(
5857
O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject},
5958
ρ::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject},
6059
) where {T1,T2}
61-
return ishermitian(O) ? real(tr(O * ρ)) : tr(O * ρ)
60+
return tr(O * ρ)
61+
end
62+
function expect(
63+
O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject},
64+
ψ::QuantumObject{<:AbstractArray{T2},KetQuantumObject},
65+
) where {TF<:Number,TR<:Real,T2}
66+
return real(dot.data, O.data, ψ.data))
67+
end
68+
function expect(
69+
O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject},
70+
ψ::QuantumObject{<:AbstractArray{T2},BraQuantumObject},
71+
) where {TF<:Number,TR<:Real,T2}
72+
return real(expect(O, ψ'))
73+
end
74+
function expect(
75+
O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject},
76+
ρ::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject},
77+
) where {TF<:Number,TR<:Real,T2}
78+
return real(tr(O * ρ))
6279
end
6380

6481
@doc raw"""

0 commit comments

Comments
 (0)