From e0936619c1fa3cce43f2e66ccd1d2710bab0424a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Tue, 13 Aug 2024 22:52:45 +0800 Subject: [PATCH 1/7] introduce `rand_unitary` --- docs/src/api.md | 1 + src/qobj/operators.jl | 47 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/docs/src/api.md b/docs/src/api.md index 54f82300..36cf8b9d 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -115,6 +115,7 @@ singlet_state triplet_states w_state ghz_state +rand_unitary sigmap sigmam sigmax diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index 8b566b8b..4b4feb0e 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -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 @@ -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(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) From a339c2cacb3c2f4376097642c2b8f043e8dd68e0 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Tue, 13 Aug 2024 22:53:27 +0800 Subject: [PATCH 2/7] introduce `isunitary` --- docs/src/api.md | 1 + src/qobj/boolean_functions.jl | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/docs/src/api.md b/docs/src/api.md index 36cf8b9d..3c27d4e4 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -38,6 +38,7 @@ issuper LinearAlgebra.ishermitian LinearAlgebra.issymmetric LinearAlgebra.isposdef +isunitary ``` ## [Qobj arithmetic and attributes](@id doc-API:Qobj-arithmetic-and-attributes) diff --git a/src/qobj/boolean_functions.jl b/src/qobj/boolean_functions.jl index b0d698ba..4ec34767 100644 --- a/src/qobj/boolean_functions.jl +++ b/src/qobj/boolean_functions.jl @@ -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) @@ -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 From a6531bed631558276553917b9d5c669b51b94325 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Tue, 13 Aug 2024 22:54:00 +0800 Subject: [PATCH 3/7] add tests for `rand_unitary` and `isunitary` --- test/quantum_objects.jl | 5 +++++ test/states_and_operators.jl | 29 ++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/test/quantum_objects.jl b/test/quantum_objects.jl index 10134f7d..eae58983 100644 --- a/test/quantum_objects.jl +++ b/test/quantum_objects.jl @@ -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 @@ -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 @@ -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) * ρ diff --git a/test/states_and_operators.jl b/test/states_and_operators.jl index 5ebbfcc9..39006331 100644 --- a/test/states_and_operators.jl +++ b/test/states_and_operators.jl @@ -189,16 +189,35 @@ @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] + 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 @@ -290,6 +309,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 From 3a44461ee5aaa66695e1b657ed8ae8226ea3a1b2 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Tue, 13 Aug 2024 23:08:23 +0800 Subject: [PATCH 4/7] add error test for `rand_unitary` --- test/states_and_operators.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/states_and_operators.jl b/test/states_and_operators.jl index 39006331..70a34522 100644 --- a/test/states_and_operators.jl +++ b/test/states_and_operators.jl @@ -200,6 +200,7 @@ @test isunitary(U4) @test U1.dims == U2.dims == [20] @test U3.dims == U4.dims == [5, 5] + @test_throws ArgumentError rand_unitary(20, :wrong) end @testset "Spin-j operators" begin From 724858a572c73065bbf3cd4927807db1bd9f112a Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Sun, 18 Aug 2024 19:52:29 +0800 Subject: [PATCH 5/7] add inference test --- test/states_and_operators.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/states_and_operators.jl b/test/states_and_operators.jl index 70a34522..56d5d679 100644 --- a/test/states_and_operators.jl +++ b/test/states_and_operators.jl @@ -200,6 +200,8 @@ @test isunitary(U4) @test U1.dims == U2.dims == [20] @test U3.dims == U4.dims == [5, 5] + @inferred QuantumObject{Matrix{ComplexF64},OperatorQuantumObject} rand_unitary(10, :haar) + @inferred QuantumObject{Matrix{ComplexF64},OperatorQuantumObject} rand_unitary(10, :exp) @test_throws ArgumentError rand_unitary(20, :wrong) end From 38197169deec9dca3e6f4634058db898e1222fad Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 19 Aug 2024 16:32:29 +0800 Subject: [PATCH 6/7] fix type inference tests --- src/qobj/operators.jl | 2 +- test/states_and_operators.jl | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/qobj/operators.jl b/src/qobj/operators.jl index 4b4feb0e..255014e7 100644 --- a/src/qobj/operators.jl +++ b/src/qobj/operators.jl @@ -43,7 +43,7 @@ function rand_unitary(dimensions::Vector{Int}, ::Val{:haar}) # 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(Q * Diagonal(Λ); type = Operator, dims = dimensions) + return QuantumObject(dense_to_sparse(Q * Diagonal(Λ)); type = Operator, dims = dimensions) end function rand_unitary(dimensions::Vector{Int}, ::Val{:exp}) N = prod(dimensions) diff --git a/test/states_and_operators.jl b/test/states_and_operators.jl index 56d5d679..210ea72a 100644 --- a/test/states_and_operators.jl +++ b/test/states_and_operators.jl @@ -200,9 +200,12 @@ @test isunitary(U4) @test U1.dims == U2.dims == [20] @test U3.dims == U4.dims == [5, 5] - @inferred QuantumObject{Matrix{ComplexF64},OperatorQuantumObject} rand_unitary(10, :haar) - @inferred QuantumObject{Matrix{ComplexF64},OperatorQuantumObject} rand_unitary(10, :exp) + @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 From 95e09770b5010a9c3db6039634d831e7531a7466 Mon Sep 17 00:00:00 2001 From: Yi-Te Huang Date: Mon, 19 Aug 2024 16:35:32 +0800 Subject: [PATCH 7/7] format files --- test/states_and_operators.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/states_and_operators.jl b/test/states_and_operators.jl index 210ea72a..e57a35f3 100644 --- a/test/states_and_operators.jl +++ b/test/states_and_operators.jl @@ -200,7 +200,7 @@ @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)