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

introduce rand_unitary and isunitary #203

Merged
merged 7 commits into from
Aug 20, 2024
Merged
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 docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ issuper
LinearAlgebra.ishermitian
LinearAlgebra.issymmetric
LinearAlgebra.isposdef
isunitary
```

## [Qobj arithmetic and attributes](@id doc-API:Qobj-arithmetic-and-attributes)
Expand Down Expand Up @@ -115,6 +116,7 @@ singlet_state
triplet_states
w_state
ghz_state
rand_unitary
sigmap
sigmam
sigmax
Expand Down
11 changes: 11 additions & 0 deletions src/qobj/boolean_functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ All boolean functions for checking the data or type in `QuantumObject`
=#

export isket, isbra, isoper, isoperbra, isoperket, issuper
export isunitary

@doc raw"""
isbra(A::QuantumObject)
Expand Down Expand Up @@ -70,3 +71,13 @@ LinearAlgebra.issymmetric(A::QuantumObject{<:AbstractArray{T}}) where {T} = issy
Test whether the [`QuantumObject`](@ref) is positive definite (and Hermitian) by trying to perform a Cholesky factorization of `A`.
"""
LinearAlgebra.isposdef(A::QuantumObject{<:AbstractArray{T}}) where {T} = isposdef(A.data)

@doc raw"""
isunitary(U::QuantumObject; kwargs...)

Test whether the [`QuantumObject`](@ref) ``U`` is unitary operator. This function calls `Base.isapprox` to test whether ``U U^\dagger`` is approximately equal to identity operator.

Note that all the keyword arguments will be passed to `Base.isapprox`.
"""
isunitary(U::QuantumObject{<:AbstractArray{T}}; kwargs...) where {T} =
isoper(U) ? isapprox(U.data * U.data', I(size(U, 1)); kwargs...) : false
47 changes: 47 additions & 0 deletions src/qobj/operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Functions for generating (common) quantum operators.
=#

export rand_unitary
export jmat, spin_Jx, spin_Jy, spin_Jz, spin_Jm, spin_Jp, spin_J_set
export sigmam, sigmap, sigmax, sigmay, sigmaz
export destroy, create, eye, projection
Expand All @@ -11,6 +12,52 @@ export commutator
export tunneling
export qft

@doc raw"""
rand_unitary(dimensions, distribution=:haar)

Returns a random unitary [`QuantumObject`](@ref).

The `dimensions` can be either the following types:
- `dimensions::Int`: Number of basis states in the Hilbert space.
- `dimensions::Vector{Int}`: list of dimensions representing the each number of basis in the subsystems.

The `distribution` specifies which of the method used to obtain the unitary matrix:
- `:haar`: Haar random unitary matrix using the algorithm from reference 1
- `:exp`: Uses ``\exp(-iH)``, where ``H`` is a randomly generated Hermitian operator.

# References
1. [F. Mezzadri, How to generate random matrices from the classical compact groups, arXiv:math-ph/0609050 (2007)](https://arxiv.org/abs/math-ph/0609050)
"""
rand_unitary(dimensions::Int, distribution::Symbol = :haar) = rand_unitary([dimensions], Val(distribution))
rand_unitary(dimensions::Vector{Int}, distribution::Symbol = :haar) = rand_unitary(dimensions, Val(distribution))
function rand_unitary(dimensions::Vector{Int}, ::Val{:haar})
ytdHuang marked this conversation as resolved.
Show resolved Hide resolved
ytdHuang marked this conversation as resolved.
Show resolved Hide resolved
N = prod(dimensions)

# generate N x N matrix Z of complex standard normal random variates
Z = randn(ComplexF64, N, N)

# find QR decomposition: Z = Q ⋅ R
Q, R = LinearAlgebra.qr(Z)

# Create a diagonal matrix Λ by rescaling the diagonal elements of R.
# Because inv(Λ) ⋅ R has real and strictly positive elements, Q · Λ is therefore Haar distributed.
Λ = diag(R) # take the diagonal elements of R
Λ ./= abs.(Λ) # rescaling the elements
return QuantumObject(dense_to_sparse(Q * Diagonal(Λ)); type = Operator, dims = dimensions)
end
function rand_unitary(dimensions::Vector{Int}, ::Val{:exp})
N = prod(dimensions)

# generate N x N matrix Z of complex standard normal random variates
Z = randn(ComplexF64, N, N)

# generate Hermitian matrix
H = QuantumObject((Z + Z') / 2; type = Operator, dims = dimensions)

return exp(-1.0im * H)
end
rand_unitary(dimensions::Vector{Int}, ::Val{T}) where {T} = throw(ArgumentError("Invalid distribution: $(T)"))

@doc raw"""
commutator(A::QuantumObject, B::QuantumObject; anti::Bool=false)

Expand Down
5 changes: 5 additions & 0 deletions test/quantum_objects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@
@test issuper(a2) == false
@test isoperket(a2) == false
@test isoperbra(a2) == false
@test isunitary(a2) == false
@test isket(a3) == true
@test isbra(a3) == false
@test isoper(a3) == false
@test issuper(a3) == false
@test isoperket(a3) == false
@test isoperbra(a3) == false
@test isunitary(a3) == false
@test Qobj(a3) == a3
@test !(Qobj(a3) === a3)
end
Expand All @@ -100,6 +102,7 @@
@test issuper(a3) == true
@test isoperket(a3) == false
@test isoperbra(a3) == false
@test isunitary(a3) == false
@test_throws DimensionMismatch Qobj(a, dims = [2])
end

Expand All @@ -117,12 +120,14 @@
@test issuper(ρ_ket) == false
@test isoperket(ρ_ket) == true
@test isoperbra(ρ_ket) == false
@test isunitary(ρ_ket) == false
@test isket(ρ_bra) == false
@test isbra(ρ_bra) == false
@test isoper(ρ_bra) == false
@test issuper(ρ_bra) == false
@test isoperket(ρ_bra) == false
@test isoperbra(ρ_bra) == true
@test isunitary(ρ_bra) == false
@test ρ_bra.dims == [2]
@test ρ_ket.dims == [2]
@test H * ρ ≈ spre(H) * ρ
Expand Down
35 changes: 32 additions & 3 deletions test/states_and_operators.jl
Original file line number Diff line number Diff line change
Expand Up @@ -189,16 +189,41 @@
@test ψx ≈ F_3_3' * ψk
end

@testset "random unitary" begin
U1 = rand_unitary(20)
U2 = rand_unitary(20, :exp)
U3 = rand_unitary([5, 5])
U4 = rand_unitary([5, 5], :exp)
@test isunitary(U1)
@test isunitary(U2)
@test isunitary(U3)
@test isunitary(U4)
@test U1.dims == U2.dims == [20]
@test U3.dims == U4.dims == [5, 5]

@test_throws ArgumentError rand_unitary(20, :wrong)
@testset "Type Inference" begin
@inferred rand_unitary(10, :haar)
@inferred rand_unitary(10, :exp)
end
end

@testset "Spin-j operators" begin
# Pauli matrices and general Spin-j operators
J0 = Qobj(spdiagm(0 => [0.0im]))
Jx, Jy, Jz = spin_J_set(0.5)
Sx = sigmax()
Sy = sigmay()
Sz = sigmaz()
@test spin_Jx(0) == spin_Jy(0) == spin_Jz(0) == spin_Jp(0) == spin_Jm(0) == J0
@test sigmax() ≈ 2 * spin_Jx(0.5) ≈ 2 * Jx ≈ Qobj(sparse([0.0im 1; 1 0]))
@test sigmay() ≈ 2 * spin_Jy(0.5) ≈ 2 * Jy ≈ Qobj(sparse([0.0im -1im; 1im 0]))
@test sigmaz() ≈ 2 * spin_Jz(0.5) ≈ 2 * Jz ≈ Qobj(sparse([1 0.0im; 0 -1]))
@test Sx ≈ 2 * spin_Jx(0.5) ≈ 2 * Jx ≈ Qobj(sparse([0.0im 1; 1 0]))
@test Sy ≈ 2 * spin_Jy(0.5) ≈ 2 * Jy ≈ Qobj(sparse([0.0im -1im; 1im 0]))
@test Sz ≈ 2 * spin_Jz(0.5) ≈ 2 * Jz ≈ Qobj(sparse([1 0.0im; 0 -1]))
@test sigmap() ≈ spin_Jp(0.5) ≈ Qobj(sparse([0.0im 1; 0 0]))
@test sigmam() ≈ spin_Jm(0.5) ≈ Qobj(sparse([0.0im 0; 1 0]))
@test isunitary(Sx)
@test isunitary(Sy)
@test isunitary(Sz)
for which in [:x, :y, :z, :+, :-]
@test jmat(2.5, which).dims == [6] # 2.5 * 2 + 1 = 6

Expand Down Expand Up @@ -290,6 +315,10 @@
I_op2 = qeye(4, dims = [2, 2])
I_su1 = qeye(4, type = SuperOperator)
I_su2 = qeye(4, type = SuperOperator, dims = [2])
@test isunitary(I_op1) == true
@test isunitary(I_op2) == true
@test isunitary(I_su1) == false
@test isunitary(I_su2) == false
@test I_op1.data == I_op2.data == I_su1.data == I_su2.data
@test (I_op1 == I_op2) == false
@test (I_op1 == I_su1) == false
Expand Down