Skip to content

Commit

Permalink
introduce rand_unitary and isunitary (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
albertomercurio authored Aug 20, 2024
2 parents 5499a17 + 95e0977 commit 74448b0
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 3 deletions.
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})
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

0 comments on commit 74448b0

Please sign in to comment.