Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix type instabilities for almost all functions #221

Merged
merged 12 commits into from
Sep 9, 2024
2 changes: 0 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ FFTW = "7a1cc6ca-52ef-59f5-83cd-3a7055c09341"
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
IncompleteLU = "40713840-3770-5561-ab4c-a76e7d0d7895"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
LinearMaps = "7a12625a-238d-50fd-b39a-03d52299707e"
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
OrdinaryDiffEqCore = "bbf590c4-e513-4bbe-9b18-05decba2e5d8"
OrdinaryDiffEqTsit5 = "b1df2697-797e-41e3-8120-5422d3b24e4a"
Expand Down Expand Up @@ -39,7 +38,6 @@ FFTW = "1.5"
Graphs = "1.7"
IncompleteLU = "0.2"
LinearAlgebra = "<0.0.1, 1"
LinearMaps = "3"
LinearSolve = "2"
OrdinaryDiffEqCore = "1"
OrdinaryDiffEqTsit5 = "1"
Expand Down
6 changes: 6 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,12 @@ wigner
negativity
```

## [Linear Maps](@id doc-API:Linear-Maps)

```@docs
AbstractLinearMap
```

## [Utility functions](@id doc-API:Utility-functions)

```@docs
Expand Down
4 changes: 2 additions & 2 deletions docs/src/users_guide/QuantumObject/QuantumObject.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ Qobj(M, dims = (2, 2)) # dims as Tuple (recommended)
Qobj(M, dims = SVector(2, 2)) # dims as StaticArrays.SVector (recommended)
```

> [!IMPORTANT]
> 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).
!!! warning "Beware of type-stability!"
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).

```@example Qobj
Qobj(rand(4, 4), type = SuperOperator)
Expand Down
2 changes: 1 addition & 1 deletion src/QuantumToolbox.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import ArrayInterface: allowed_getindex, allowed_setindex!
import FFTW: fft, fftshift
import Graphs: connected_components, DiGraph
import IncompleteLU: ilu
import LinearMaps: LinearMap
import Pkg
import Random
import SpecialFunctions: loggamma
Expand All @@ -54,6 +53,7 @@ BLAS.set_num_threads(1)
include("utilities.jl")
include("versioninfo.jl")
include("progress_bar.jl")
include("linear_maps.jl")

# Quantum Object
include("qobj/quantum_object.jl")
Expand Down
54 changes: 54 additions & 0 deletions src/linear_maps.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export AbstractLinearMap

@doc raw"""
AbstractLinearMap{T, TS}

Represents a general linear map with element type `T` and size `TS`.

## Overview

A **linear map** is a transformation `L` that satisfies:

- **Additivity**:
```math
L(u + v) = L(u) + L(v)
```
- **Homogeneity**:
```math
L(cu) = cL(u)
```

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.

## Methods

- `Base.eltype(A)`: Returns the element type `T`.
- `Base.size(A)`: Returns the size `A.size`.
- `Base.size(A, i)`: Returns the `i`-th dimension.

## Example

As an example, we now define the linear map used in the [`eigsolve_al`](@ref) function for Arnoldi-Lindblad eigenvalue solver:

```julia-repl
struct ArnoldiLindbladIntegratorMap{T,TS,TI} <: AbstractLinearMap{T,TS}
elty::Type{T}
size::TS
integrator::TI
end

function LinearAlgebra.mul!(y::AbstractVector, A::ArnoldiLindbladIntegratorMap, x::AbstractVector)
reinit!(A.integrator, x)
solve!(A.integrator)
return copyto!(y, A.integrator.u)
end
```

where `integrator` is the ODE integrator for the time-evolution. In this way, we can diagonalize this linear map using the [`eigsolve`](@ref) function.
"""
abstract type AbstractLinearMap{T,TS} end

Base.eltype(A::AbstractLinearMap{T}) where {T} = T

Base.size(A::AbstractLinearMap) = A.size

Check warning on line 53 in src/linear_maps.jl

View check run for this annotation

Codecov / codecov/patch

src/linear_maps.jl#L53

Added line #L53 was not covered by tests
Base.size(A::AbstractLinearMap, i::Int) = A.size[i]
8 changes: 4 additions & 4 deletions src/metrics.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ function entropy_vn(
base::Int = 0,
tol::Real = 1e-15,
) where {T}
vals = eigvals(ρ)
indexes = abs.(vals) .> tol
1 ∉ indexes && return 0
vals = eigenenergies(ρ)
indexes = findall(x -> abs(x) > tol, vals)
length(indexes) == 0 && return zero(real(T))
nzvals = vals[indexes]
logvals = base != 0 ? log.(base, Complex.(nzvals)) : log.(Complex.(nzvals))
return -real(sum(nzvals .* logvals))
return -real(mapreduce(*, +, nzvals, logvals))
end

"""
Expand Down
21 changes: 13 additions & 8 deletions src/qobj/arithmetic_and_attributes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,10 @@
"""
LinearAlgebra.tr(
A::QuantumObject{<:AbstractArray{T},OpType},
) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} =
ishermitian(A) ? real(tr(A.data)) : tr(A.data)
) where {T,OpType<:Union{OperatorQuantumObject,SuperOperatorQuantumObject}} = tr(A.data)
LinearAlgebra.tr(

Check warning on line 243 in src/qobj/arithmetic_and_attributes.jl

View check run for this annotation

Codecov / codecov/patch

src/qobj/arithmetic_and_attributes.jl#L243

Added line #L243 was not covered by tests
A::QuantumObject{<:Union{<:Hermitian{TF},Symmetric{TR}},OpType},
) where {TF<:BlasFloat,TR<:Real,OpType<:OperatorQuantumObject} = real(tr(A.data))

@doc raw"""
svdvals(A::QuantumObject)
Expand Down Expand Up @@ -515,7 +517,7 @@
length(QO.dims) == 1 && return QO

ρtr, dkeep = _ptrace_ket(QO.data, QO.dims, SVector(sel))
return QuantumObject(ρtr, dims = dkeep)
return QuantumObject(ρtr, type = Operator, dims = dkeep)
end

ptrace(QO::QuantumObject{<:AbstractArray,BraQuantumObject}, sel::Union{AbstractVector{Int},Tuple}) = ptrace(QO', sel)
Expand All @@ -524,7 +526,7 @@
length(QO.dims) == 1 && return QO

ρtr, dkeep = _ptrace_oper(QO.data, QO.dims, SVector(sel))
return QuantumObject(ρtr, dims = dkeep)
return QuantumObject(ρtr, type = Operator, dims = dkeep)
end
ptrace(QO::QuantumObject, sel::Int) = ptrace(QO, SVector(sel))

Expand All @@ -547,7 +549,7 @@

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

return vmat * vmat', dkeep
Expand Down Expand Up @@ -576,14 +578,14 @@

ρmat = reshape(QO, reverse(vcat(dims, dims))...)
topermute = 2 * nd + 1 .- qtrace_sel
ρmat = PermutedDimsArray(ρmat, reverse(topermute))
ρmat = permutedims(ρmat, reverse(topermute)) # TODO: use PermutedDimsArray when Julia v1.11.0 is released
ytdHuang marked this conversation as resolved.
Show resolved Hide resolved

## TODO: Check if it works always

# ρmat = row_major_reshape(ρmat, prod(dtrace), prod(dtrace), prod(dkeep), prod(dkeep))
# res = dropdims(mapslices(tr, ρmat, dims=(1,2)), dims=(1,2))
ρmat = reshape(ρmat, prod(dkeep), prod(dkeep), prod(dtrace), prod(dtrace))
res = dropdims(mapslices(tr, ρmat, dims = (3, 4)), dims = (3, 4))
res = map(tr, eachslice(ρmat, dims = (1, 2)))

return res, dkeep
end
Expand Down Expand Up @@ -673,6 +675,9 @@
julia> permute(ψ_123, [2, 1, 3]) ≈ tensor(ψ2, ψ1, ψ3)
true
```

!!! warning "Beware of type-stability!"
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.
"""
function permute(
A::QuantumObject{<:AbstractArray{T},ObjType},
Expand All @@ -691,7 +696,7 @@
dims, perm = _dims_and_perm(A.type, A.dims, order_svector, length(order_svector))

return QuantumObject(
reshape(PermutedDimsArray(reshape(A.data, dims...), Tuple(perm)), size(A)),
reshape(permutedims(reshape(A.data, dims...), Tuple(perm)), size(A)),
A.type,
A.dims[order_svector],
)
Expand Down
35 changes: 24 additions & 11 deletions src/qobj/eigsolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,27 @@ function Base.show(io::IO, res::EigsolveResult)
return show(io, MIME("text/plain"), res.vectors)
end

function _map_ldiv(linsolve, y, x)
linsolve.b .= x
return y .= solve!(linsolve).u
struct ArnoldiLindbladIntegratorMap{T,TS,TI} <: AbstractLinearMap{T,TS}
elty::Type{T}
size::TS
integrator::TI
end

function LinearAlgebra.mul!(y::AbstractVector, A::ArnoldiLindbladIntegratorMap, x::AbstractVector)
reinit!(A.integrator, x)
solve!(A.integrator)
return copyto!(y, A.integrator.u)
end

struct EigsolveInverseMap{T,TS,TI} <: AbstractLinearMap{T,TS}
elty::Type{T}
size::TS
linsolve::TI
end

function LinearAlgebra.mul!(y::AbstractVector, A::EigsolveInverseMap, x::AbstractVector)
A.linsolve.b .= x
return copyto!(y, solve!(A.linsolve).u)
end

function _permuteschur!(
Expand Down Expand Up @@ -293,7 +311,8 @@ function eigsolve(

prob = LinearProblem(Aₛ, v0)
linsolve = init(prob, solver; kwargs2...)
Amap = LinearMap{T}((y, x) -> _map_ldiv(linsolve, y, x), length(v0))

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

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

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

function arnoldi_lindblad_solve(ρ)
reinit!(integrator, ρ)
solve!(integrator)
return integrator.u
end

Lmap = LinearMap{eltype(MT1)}(arnoldi_lindblad_solve, size(L, 1), ismutating = false)
Lmap = ArnoldiLindbladIntegratorMap(eltype(MT1), size(L), integrator)

res = _eigsolve(Lmap, mat2vec(ρ0), L.type, L.dims, k, krylovdim, maxiter = maxiter, tol = eigstol)
# finish!(prog)
Expand Down
37 changes: 27 additions & 10 deletions src/qobj/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

If `ψ` is a density matrix ([`Operator`](@ref)), the function calculates ``\textrm{Tr} \left[ \hat{O} \hat{\psi} \right]``

The function returns a real number if `O` is hermitian, and returns a complex number otherwise.
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)`.

# Examples

Expand All @@ -34,31 +34,48 @@

julia> a = destroy(10);

julia> expect(a' * a, ψ) ≈ 3
true
julia> expect(a' * a, ψ) |> round
3.0 + 0.0im

julia> expect(Hermitian(a' * a), ψ) |> round
3.0
```
"""
function expect(
O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject},
ψ::QuantumObject{<:AbstractArray{T2},KetQuantumObject},
) where {T1,T2}
ψd = ψ.data
Od = O.data
return ishermitian(O) ? real(dot(ψd, Od, ψd)) : dot(ψd, Od, ψd)
return dot(ψ.data, O.data, ψ.data)
end
function expect(
O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject},
ψ::QuantumObject{<:AbstractArray{T2},BraQuantumObject},
) where {T1,T2}
ψd = ψ.data'
Od = O.data
return ishermitian(O) ? real(dot(ψd, Od, ψd)) : dot(ψd, Od, ψd)
return expect(O, ψ')
end
function expect(
O::QuantumObject{<:AbstractArray{T1},OperatorQuantumObject},
ρ::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject},
) where {T1,T2}
return ishermitian(O) ? real(tr(O * ρ)) : tr(O * ρ)
return tr(O * ρ)
end
function expect(

Check warning on line 62 in src/qobj/functions.jl

View check run for this annotation

Codecov / codecov/patch

src/qobj/functions.jl#L62

Added line #L62 was not covered by tests
O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject},
ψ::QuantumObject{<:AbstractArray{T2},KetQuantumObject},
) where {TF<:Number,TR<:Real,T2}
return real(dot(ψ.data, O.data, ψ.data))

Check warning on line 66 in src/qobj/functions.jl

View check run for this annotation

Codecov / codecov/patch

src/qobj/functions.jl#L66

Added line #L66 was not covered by tests
end
function expect(

Check warning on line 68 in src/qobj/functions.jl

View check run for this annotation

Codecov / codecov/patch

src/qobj/functions.jl#L68

Added line #L68 was not covered by tests
O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject},
ψ::QuantumObject{<:AbstractArray{T2},BraQuantumObject},
) where {TF<:Number,TR<:Real,T2}
return real(expect(O, ψ'))

Check warning on line 72 in src/qobj/functions.jl

View check run for this annotation

Codecov / codecov/patch

src/qobj/functions.jl#L72

Added line #L72 was not covered by tests
end
function expect(
O::QuantumObject{<:Union{<:Hermitian{TF},<:Symmetric{TR}},OperatorQuantumObject},
ρ::QuantumObject{<:AbstractArray{T2},OperatorQuantumObject},
) where {TF<:Number,TR<:Real,T2}
return real(tr(O * ρ))
end

@doc raw"""
Expand Down
Loading
Loading