From 353d689fcfd307eeb14382df1caf27177124e1c7 Mon Sep 17 00:00:00 2001 From: apkille Date: Mon, 24 Jun 2024 11:59:02 -0400 Subject: [PATCH 01/22] superop and trace additions --- src/QSymbolicsBase/QSymbolicsBase.jl | 25 +++++---- src/QSymbolicsBase/basic_ops_homogeneous.jl | 2 +- src/QSymbolicsBase/basic_ops_inhomogeneous.jl | 3 +- src/QSymbolicsBase/literal_objects.jl | 18 +++++- src/QSymbolicsBase/predefined.jl | 55 ++++++++++++++++++- src/QSymbolicsBase/repr_CPTP.jl | 19 +++++++ 6 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 src/QSymbolicsBase/repr_CPTP.jl diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index ac50922..5c0eb6b 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -1,20 +1,20 @@ using Symbolics import Symbolics: simplify using SymbolicUtils -import SymbolicUtils: Symbolic, _isone, flatten_term, isnotflat, Chain, Fixpoint +import SymbolicUtils: Symbolic,_isone,flatten_term,isnotflat,Chain,Fixpoint using TermInterface -import TermInterface: isexpr, head, iscall, children, operation, arguments, metadata +import TermInterface: isexpr,head,iscall,children,operation,arguments,metadata using LinearAlgebra -import LinearAlgebra: eigvecs, ishermitian, inv +import LinearAlgebra: eigvecs,ishermitian,inv import QuantumInterface: apply!, tensor, ⊗, - basis, Basis, SpinBasis, FockBasis, + basis,Basis,SpinBasis,FockBasis, nqubits, - projector, dagger, - AbstractKet, AbstractOperator, AbstractSuperOperator, AbstractBra + projector,dagger,tr,ptrace,vec, + AbstractBra,AbstractKet,AbstractOperator,AbstractSuperOperator export SymQObj,QObj, AbstractRepresentation,AbstractUse, @@ -23,26 +23,28 @@ export SymQObj,QObj, apply!, express, tensor,⊗, - dagger,projector,commutator,anticommutator,expand, + dagger,projector,commutator,anticommutator,expand,tr,ptrace, I,X,Y,Z,σˣ,σʸ,σᶻ,Pm,Pp,σ₋,σ₊, H,CNOT,CPHASE,XCX,XCY,XCZ,YCX,YCY,YCZ,ZCX,ZCY,ZCZ, X1,X2,Y1,Y2,Z1,Z2,X₁,X₂,Y₁,Y₂,Z₁,Z₂,L0,L1,Lp,Lm,Lpi,Lmi,L₀,L₁,L₊,L₋,L₊ᵢ,L₋ᵢ, vac,F₀,F0,F₁,F1, N,n̂,Create,âꜛ,Destroy,â,SpinBasis,FockBasis, - SBra,SKet,SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator, - @ket,@bra,@op, + SBra,SKet,SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator,SSuperOperator, + @ket,@bra,@op,@superop, SAdd,SAddBra,SAddKet,SAddOperator, SScaled,SScaledBra,SScaledOperator,SScaledKet, STensorBra,STensorKet,STensorOperator, SZeroBra,SZeroKet,SZeroOperator, - SProjector,MixedState,IdentityOp,SInvOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator, + SProjector,MixedState,IdentityOp,SInvOperator, SApplyKet,SApplyBra,SMulOperator,SSuperOpApply,SCommutator,SAnticommutator,SDagger,SBraKet,SOuterKetBra, HGate,XGate,YGate,ZGate,CPHASEGate,CNOTGate, XBasisState,YBasisState,ZBasisState, NumberOp,CreateOp,DestroyOp, XCXGate,XCYGate,XCZGate,YCXGate,YCYGate,YCZGate,ZCXGate,ZCYGate,ZCZGate, qsimplify,qsimplify_pauli,qsimplify_flatten,qsimplify_commutator,qsimplify_anticommutator, - isunitary + isunitary, + KrausRepr, + kraus function countmap(samples) # A simpler version of StatsBase.countmap, because StatsBase is slow to import counts = Dict{Any,Any}() @@ -171,6 +173,7 @@ propsequal(x,y) = all(n->isequal(getproperty(x,n),getproperty(y,n)), propertynam ## include("literal_objects.jl") +include("repr_CPTP.jl") include("basic_ops_homogeneous.jl") include("basic_ops_inhomogeneous.jl") include("predefined.jl") diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index 176feeb..7429c63 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -164,7 +164,7 @@ iscall(::STensor) = true arguments(x::STensor) = x.terms operation(x::STensor) = ⊗ head(x::STensor) = :⊗ -children(x::STensor) = pushfirst!(x.terms,:⊗) +children(x::STensor) = [:⊗; x.terms] function ⊗(xs::Symbolic{T}...) where {T<:QObj} zero_ind = findfirst(x->iszero(x), xs) isnothing(zero_ind) ? STensor{T}(collect(xs)) : SZero{T}() diff --git a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl index 49c48ae..9daa9fc 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -103,7 +103,8 @@ Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::Symbolic{AbstractOperator}) Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::SZeroOperator) = SZeroOperator() Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::Symbolic{AbstractKet}) = SSuperOpApply(sop,SProjector(k)) Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::SZeroKet) = SZeroKet() -Base.show(io::IO, x::SSuperOpApply) = begin print(io, x.sop); print(io, x.op) end +Base.:(*)(sop::KrausRepr, op::Symbolic{AbstractOperator}) = (+)((i*op*dagger(i) for i in sop.krausops)...) +Base.show(io::IO, x::SSuperOpApply) = print(io, "$(x.sop)[$(x.op)]") basis(x::SSuperOpApply) = basis(x.op) """Symbolic outer product of a ket and a bra diff --git a/src/QSymbolicsBase/literal_objects.jl b/src/QSymbolicsBase/literal_objects.jl index d6ebfbc..a65c850 100644 --- a/src/QSymbolicsBase/literal_objects.jl +++ b/src/QSymbolicsBase/literal_objects.jl @@ -67,7 +67,21 @@ SHermitianUnitaryOperator(name) = SHermitianUnitaryOperator(name, qubit_basis) ishermitian(::SHermitianUnitaryOperator) = true isunitary(::SHermitianUnitaryOperator) = true -const SymQ = Union{SKet,SBra,SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator} +struct SSuperOperator <: Symbolic{AbstractSuperOperator} + name::Symbol + basis::Basis +end +SSuperOperator(name) = SSuperOperator(name, qubit_basis) +macro superop(name, basis) + :($(esc(name)) = SSuperOperator($(Expr(:quote, name)), $(basis))) +end +macro superop(name) + :($(esc(name)) = SSuperOperator($(Expr(:quote, name)))) +end +ishermitian(x::SSuperOperator) = false +isunitary(x::SSuperOperator) = false + +const SymQ = Union{SKet,SBra,SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator,SSuperOperator} isexpr(::SymQ) = false metadata(::SymQ) = nothing symbollabel(x::SymQ) = x.name @@ -75,7 +89,7 @@ basis(x::SymQ) = x.basis Base.show(io::IO, x::SKet) = print(io, "|$(symbollabel(x))⟩") Base.show(io::IO, x::SBra) = print(io, "⟨$(symbollabel(x))|") -Base.show(io::IO, x::Union{SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator}) = print(io, "$(symbollabel(x))") +Base.show(io::IO, x::Union{SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator,SSuperOperator}) = print(io, "$(symbollabel(x))") Base.show(io::IO, x::SymQObj) = print(io, symbollabel(x)) # fallback that probably is not great struct SZero{T<:QObj} <: Symbolic{T} end diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 182e8bc..b25a07a 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -288,6 +288,59 @@ function Base.show(io::IO, x::SDagger) end symbollabel(x::SDagger) = symbollabel(x.obj) +"""Trace of an operator + +```jldoctest +julia> @op A; @op B; + +julia> tr(A) +tr(A) + +julia> tr(A⊗B) +tr(B)*tr(A) + +julia> tr(commutator(A, B)) +0 + +julia> @bra b; @ket k; + +julia> tr(k*b) +⟨b||k⟩ +``` +""" +@withmetadata struct STrace <: Symbolic{Complex} + op::Symbolic{AbstractOperator} +end +isexpr(::STrace) = true +iscall(::STrace) = true +arguments(x::STrace) = [x.op] +operation(x::STrace) = tr +head(x::STrace) = :tr +children(x::STrace) = [:tr, x.op] +basis(x::STrace) = basis(x.op) +Base.show(io::IO, x::STrace) = print(io, "tr($(x.op))") +tr(x::Symbolic{AbstractOperator}) = STrace(x) +tr(x::SScaled{AbstractOperator}) = x.coeff*tr(x.obj) +tr(x::SAdd{AbstractOperator}) = (+)((tr(i) for i in arguments(x))...) +tr(x::SOuterKetBra) = x.bra*x.ket +tr(x::SCommutator) = 0 +tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) # TODO add tr properties + +@withmetadata struct SVec <: Symbolic{AbstractKet} + op::Symbolic{AbstractOperator} +end +isexpr(::SVec) = true +iscall(::SVec) = true +arguments(x::SVec) = [x.op] +operation(x::SVec) = vec +head(x::SVec) = :vec +children(x::SVec) = [:vec, x.op] +basis(x::SVec) = basis(x.op) +Base.show(io::IO, x::SVec) = print(io, "vec($(x.op))") +vec(x::Symbolic{AbstractOperator}) = SVec(x) +vec(x::SScaled{AbstractOperator}) = x.coeff*vec(x.obj) +vec(x::SAdd{AbstractOperator}) = (+)((vec(i) for i in arguments(x))...) # TODO add vec properties + """Inverse Operator ```jldoctest @@ -368,4 +421,4 @@ ishermitian(::IdentityOp) = true isunitary(::IdentityOp) = true """Identity operator in qubit basis""" -const I = IdentityOp(qubit_basis) \ No newline at end of file +const I = IdentityOp(qubit_basis) \ No newline at end of file diff --git a/src/QSymbolicsBase/repr_CPTP.jl b/src/QSymbolicsBase/repr_CPTP.jl new file mode 100644 index 0000000..f76ec33 --- /dev/null +++ b/src/QSymbolicsBase/repr_CPTP.jl @@ -0,0 +1,19 @@ +## +# Representations of CPTP maps +## + +@withmetadata struct KrausRepr <: Symbolic{AbstractSuperOperator} + name::Symbol + basis::Basis + krausops +end +isexpr(::KrausRepr) = true +iscall(::KrausRepr) = true +arguments(x::KrausRepr) = x.terms +operation(x::KrausRepr) = kraus +head(x::KrausRepr) = :kraus +children(x::KrausRepr) = [:kraus; x.terms] +kraus(n::Symbol, b::Basis, xs::Symbolic{AbstractOperator}...) = KrausRepr(n,b,collect(xs)) +symbollabel(x::KrausRepr) = x.name +basis(x::KrausRepr) = x.basis +Base.show(io::IO, x::KrausRepr) = print(io, symbollabel(x)) From 19e1b3fcb6b9079e0fab2e8ef5ed144a0a23a4bb Mon Sep 17 00:00:00 2001 From: apkille Date: Mon, 24 Jun 2024 16:13:01 -0400 Subject: [PATCH 02/22] ptrace additions --- src/QSymbolicsBase/literal_objects.jl | 2 +- src/QSymbolicsBase/predefined.jl | 49 +++++++++++++++++++++++++++ src/QSymbolicsBase/rules.jl | 3 +- 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/QSymbolicsBase/literal_objects.jl b/src/QSymbolicsBase/literal_objects.jl index a65c850..95a808b 100644 --- a/src/QSymbolicsBase/literal_objects.jl +++ b/src/QSymbolicsBase/literal_objects.jl @@ -106,4 +106,4 @@ symbollabel(x::SZero) = "𝟎" basis(x::SZero) = nothing Base.show(io::IO, x::SZero) = print(io, symbollabel(x)) -Base.iszero(x::SymQObj) = isa(x, SZero) +Base.iszero(x::Union{SymQObj, Symbolic{Complex}}) = isa(x, SZero) diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index b25a07a..e1fbd5c 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -326,6 +326,55 @@ tr(x::SOuterKetBra) = x.bra*x.ket tr(x::SCommutator) = 0 tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) # TODO add tr properties +"""Partial trace over system i of a composite quantum system + +```jldoctest +julia> @op A; @op B; + +julia> ptrace(A⊗B, 1)) +tr1(A⊗B) + +julia> @ket k; @bra b; + +julia> comp = A ⊗ (k*b) +A⊗|k⟩⟨b| + +julia> ptrace(comp, 1) +(tr(A))|k⟩⟨b| + +julia> ptrace(comp, 2) +(⟨b||k⟩)A +``` +""" +@withmetadata struct SPartialTrace <: Symbolic{Complex} + obj + sys::Int +end +isexpr(::SPartialTrace) = true +iscall(::SPartialTrace) = true +arguments(x::SPartialTrace) = [x.obj, x.sys] +operation(x::SPartialTrace) = ptrace +head(x::SPartialTrace) = :ptrace +children(x::SPartialTrace) = [:ptrace, x.obj, x.sys] +#basis(x::SPartialTrace) = basis(x.op) +Base.show(io::IO, x::SPartialTrace) = print(io, "tr$(x.sys)($(x.obj))") +ptrace(x::Symbolic{AbstractOperator}, s) = SPartialTrace(x, s) +function ptrace(x::STensorOperator, s) + terms = arguments(x) + sys_op = terms[s] + tr(sys_op)*STensorOperator(deleteat!(copy(terms), s)) +end +function ptrace(x::SAddOperator, s) + terms = arguments(x) + new_terms = [] + for i in terms + isa(i, SScaledOperator) ? prod_terms = arguments(i.obj) : prod_terms = arguments(i) + sys_op = prod_terms[s] + push!(new_terms, tr(sys_op)*STensorOperator(deleteat!(copy(prod_terms), s))) + end + new_terms +end + @withmetadata struct SVec <: Symbolic{AbstractKet} op::Symbolic{AbstractOperator} end diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index 6a8d6b7..f85e909 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -12,6 +12,7 @@ function hasscalings(xs) end _isa(T) = x->isa(x,T) + ## # Determining factors for expressions containing quantum objects ## @@ -22,7 +23,7 @@ function prefactorscalings(xs; scalar=false) # If the scalar keyword is true, th for x in xs if isexpr(x) && operation(x) == * c,t = arguments(x) - if !scalar + if !scalar && !isa(x, SOuterKetBra) coeff *= c push!(terms,t) elseif scalar && c isa Number From 1af6b2adcab05f54ac0d49d78236386ce900ab28 Mon Sep 17 00:00:00 2001 From: apkille Date: Tue, 25 Jun 2024 11:24:20 -0400 Subject: [PATCH 03/22] tests --- src/QSymbolicsBase/basic_ops_homogeneous.jl | 14 ++++---- src/QSymbolicsBase/literal_objects.jl | 2 +- src/QSymbolicsBase/predefined.jl | 38 ++++++++++++++++----- src/QSymbolicsBase/rules.jl | 3 +- test/test_trace.jl | 14 ++++++++ 5 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 test/test_trace.jl diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index 7429c63..2655537 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -93,6 +93,11 @@ end Base.:(+)(xs::Vararg{Symbolic{<:QObj},0}) = 0 # to avoid undefined type parameters issue in the above method basis(x::SAdd) = basis(first(x.dict).first) +const SAddBra = SAdd{AbstractBra} +function Base.show(io::IO, x::SAddBra) + ordered_terms = sort([repr(i) for i in arguments(x)]) + print(io, "("*join(ordered_terms,"+")::String*")") # type assert to help inference +end const SAddKet = SAdd{AbstractKet} function Base.show(io::IO, x::SAddKet) ordered_terms = sort([repr(i) for i in arguments(x)]) @@ -103,11 +108,6 @@ function Base.show(io::IO, x::SAddOperator) ordered_terms = sort([repr(i) for i in arguments(x)]) print(io, "("*join(ordered_terms,"+")::String*")") # type assert to help inference end -const SAddBra = SAdd{AbstractBra} -function Base.show(io::IO, x::SAddBra) - ordered_terms = sort([repr(i) for i in arguments(x)]) - print(io, "("*join(ordered_terms,"+")::String*")") # type assert to help inference -end """Symbolic application of operator on operator @@ -171,14 +171,14 @@ function ⊗(xs::Symbolic{T}...) where {T<:QObj} end basis(x::STensor) = tensor(basis.(x.terms)...) +const STensorBra = STensor{AbstractBra} +Base.show(io::IO, x::STensorBra) = print(io, join(map(string, arguments(x)),"")) const STensorKet = STensor{AbstractKet} Base.show(io::IO, x::STensorKet) = print(io, join(map(string, arguments(x)),"")) const STensorOperator = STensor{AbstractOperator} Base.show(io::IO, x::STensorOperator) = print(io, join(map(string, arguments(x)),"⊗")) const STensorSuperOperator = STensor{AbstractSuperOperator} Base.show(io::IO, x::STensorSuperOperator) = print(io, join(map(string, arguments(x)),"⊗")) -const STensorBra = STensor{AbstractBra} -Base.show(io::IO, x::STensorBra) = print(io, join(map(string, arguments(x)),"")) """Symbolic commutator of two operators diff --git a/src/QSymbolicsBase/literal_objects.jl b/src/QSymbolicsBase/literal_objects.jl index 95a808b..b62c963 100644 --- a/src/QSymbolicsBase/literal_objects.jl +++ b/src/QSymbolicsBase/literal_objects.jl @@ -106,4 +106,4 @@ symbollabel(x::SZero) = "𝟎" basis(x::SZero) = nothing Base.show(io::IO, x::SZero) = print(io, symbollabel(x)) -Base.iszero(x::Union{SymQObj, Symbolic{Complex}}) = isa(x, SZero) +Base.iszero(x::Union{SymQObj, Symbolic{Number}, Symbolic{Complex}}) = isa(x, SZero) diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index e1fbd5c..705f4ae 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -331,19 +331,28 @@ tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) # TODO add tr pro ```jldoctest julia> @op A; @op B; -julia> ptrace(A⊗B, 1)) +julia> ptrace(A⊗B, 1) tr1(A⊗B) julia> @ket k; @bra b; -julia> comp = A ⊗ (k*b) +julia> pure_state = A ⊗ (k*b) A⊗|k⟩⟨b| -julia> ptrace(comp, 1) +julia> ptrace(pure_state, 1) (tr(A))|k⟩⟨b| -julia> ptrace(comp, 2) +julia> ptrace(pure_state, 2) (⟨b||k⟩)A + +julia> mixed_state = (A⊗(k*b)) + ((k*b)⊗B) +(A⊗|k⟩⟨b|+|k⟩⟨b|⊗B) + +julia> ptrace(mixed_state, 1) +((0 + ⟨b||k⟩)B+(tr(A))|k⟩⟨b|) + +julia> ptrace(mixed_state, 2) +((0 + ⟨b||k⟩)A+(tr(B))|k⟩⟨b|) ``` """ @withmetadata struct SPartialTrace <: Symbolic{Complex} @@ -368,13 +377,26 @@ function ptrace(x::SAddOperator, s) terms = arguments(x) new_terms = [] for i in terms - isa(i, SScaledOperator) ? prod_terms = arguments(i.obj) : prod_terms = arguments(i) - sys_op = prod_terms[s] - push!(new_terms, tr(sys_op)*STensorOperator(deleteat!(copy(prod_terms), s))) + if isexpr(i) && operation(i) === ⊗ + isa(i, SScaledOperator) ? prod_terms = arguments(i.obj) : prod_terms = arguments(i) + sys_op = prod_terms[s] + push!(new_terms, tr(sys_op)*STensorOperator(deleteat!(copy(prod_terms), s))) + else + throw(ArgumentError("cannot take partial trace of a single quantum system")) + end end - new_terms + (+)(new_terms...) end +"""Vectorization of a symbolic operator + +```jldoctest +julia> @op A; + +julia> vec(A) +vec(A) +``` +""" @withmetadata struct SVec <: Symbolic{AbstractKet} op::Symbolic{AbstractOperator} end diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index f85e909..df747c4 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -12,7 +12,6 @@ function hasscalings(xs) end _isa(T) = x->isa(x,T) - ## # Determining factors for expressions containing quantum objects ## @@ -23,7 +22,7 @@ function prefactorscalings(xs; scalar=false) # If the scalar keyword is true, th for x in xs if isexpr(x) && operation(x) == * c,t = arguments(x) - if !scalar && !isa(x, SOuterKetBra) + if !scalar && !isa(x,SOuterKetBra) coeff *= c push!(terms,t) elseif scalar && c isa Number diff --git a/test/test_trace.jl b/test/test_trace.jl new file mode 100644 index 0000000..eb39720 --- /dev/null +++ b/test/test_trace.jl @@ -0,0 +1,14 @@ +using QuantumSymbolics +using Test + +@bra b₁; @bra b₂; +@ket k₁; @ket k₂; +@op A; @op B; @op C; + +@testset "trace tests" begin + @test isequal(tr(2*A), 2*tr(A)) + @test isequal(tr(A+B), tr(A)+tr(B)) + @test isequal(tr(k₁*b₁), b₁*k₁) + @test isequal(tr(commutator(A, B)), 0) + @test isequal(tr((⊗)(A, B, C)), tr(A)*tr(B)*tr(C)) +end From 45a42249693a39b6cfbfead28986e2fea67f07d8 Mon Sep 17 00:00:00 2001 From: apkille Date: Tue, 25 Jun 2024 15:46:03 -0400 Subject: [PATCH 04/22] examples and redef --- src/QSymbolicsBase/QSymbolicsBase.jl | 3 +- src/QSymbolicsBase/basic_ops_homogeneous.jl | 8 ++++- src/QSymbolicsBase/basic_ops_inhomogeneous.jl | 9 ++++- src/QSymbolicsBase/predefined.jl | 12 ++++--- src/QSymbolicsBase/predefined_CPTP.jl | 34 +++++++++++++++++++ src/QSymbolicsBase/repr_CPTP.jl | 19 ----------- 6 files changed, 58 insertions(+), 27 deletions(-) delete mode 100644 src/QSymbolicsBase/repr_CPTP.jl diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index 5c0eb6b..e8a8b93 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -173,11 +173,10 @@ propsequal(x,y) = all(n->isequal(getproperty(x,n),getproperty(y,n)), propertynam ## include("literal_objects.jl") -include("repr_CPTP.jl") +include("predefined_CPTP.jl") include("basic_ops_homogeneous.jl") include("basic_ops_inhomogeneous.jl") include("predefined.jl") -include("predefined_CPTP.jl") ## # Symbolic and simplification rules diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index 2655537..648b0de 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -121,7 +121,7 @@ AB @withmetadata struct SMulOperator <: Symbolic{AbstractOperator} terms function SMulOperator(terms) - coeff, cleanterms = prefactorscalings(terms) + coeff, cleanterms = prefactorscalings(terms, scalar=true) coeff*new(cleanterms) end end @@ -188,6 +188,9 @@ julia> @op A; @op B; julia> commutator(A, B) [A,B] +julia> expand(commutator(A, B)) +(-1BA+AB) + julia> commutator(A, A) 𝟎 ``` @@ -221,6 +224,9 @@ julia> @op A; @op B; julia> anticommutator(A, B) {A,B} + +julia> expand(anticommutator(A, B)) +(AB+BA) ``` """ @withmetadata struct SAnticommutator <: Symbolic{AbstractOperator} diff --git a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl index 9daa9fc..681531e 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -88,7 +88,14 @@ Base.:(*)(b::SZeroBra, k::SZeroKet) = 0 Base.show(io::IO, x::SBraKet) = begin print(io,x.bra); print(io,x.ket) end Base.isequal(x::SBraKet, y::SBraKet) = isequal(x.bra, y.bra) && isequal(x.ket, y.ket) -"""Symbolic application of a superoperator on an operator""" +"""Symbolic application of a superoperator on an operator + +```jldoctest +julia> @op A; @superop S; + +julia> S*A +S[A] +""" @withmetadata struct SSuperOpApply <: Symbolic{AbstractOperator} sop op diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 705f4ae..75bc91a 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -297,7 +297,7 @@ julia> tr(A) tr(A) julia> tr(A⊗B) -tr(B)*tr(A) +tr(A)*tr(B) julia> tr(commutator(A, B)) 0 @@ -325,14 +325,18 @@ tr(x::SAdd{AbstractOperator}) = (+)((tr(i) for i in arguments(x))...) tr(x::SOuterKetBra) = x.bra*x.ket tr(x::SCommutator) = 0 tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) # TODO add tr properties +Base.isequal(x::STrace, y::STrace) = isequal(x.op, y.op) """Partial trace over system i of a composite quantum system ```jldoctest julia> @op A; @op B; +julia> ptrace(A, 1) +tr1(A) + julia> ptrace(A⊗B, 1) -tr1(A⊗B) +(tr(A))B julia> @ket k; @bra b; @@ -394,7 +398,7 @@ end julia> @op A; julia> vec(A) -vec(A) +|A⟩⟩ ``` """ @withmetadata struct SVec <: Symbolic{AbstractKet} @@ -407,7 +411,7 @@ operation(x::SVec) = vec head(x::SVec) = :vec children(x::SVec) = [:vec, x.op] basis(x::SVec) = basis(x.op) -Base.show(io::IO, x::SVec) = print(io, "vec($(x.op))") +Base.show(io::IO, x::SVec) = print(io, "|$(x.op)⟩⟩") vec(x::Symbolic{AbstractOperator}) = SVec(x) vec(x::SScaled{AbstractOperator}) = x.coeff*vec(x.obj) vec(x::SAdd{AbstractOperator}) = (+)((vec(i) for i in arguments(x))...) # TODO add vec properties diff --git a/src/QSymbolicsBase/predefined_CPTP.jl b/src/QSymbolicsBase/predefined_CPTP.jl index c53fde7..c6415c5 100644 --- a/src/QSymbolicsBase/predefined_CPTP.jl +++ b/src/QSymbolicsBase/predefined_CPTP.jl @@ -47,3 +47,37 @@ function Base.show(io::IO, x::GateCPTP) print(io, x.gate) print(io, "]") end + +## +# Representations of CPTP maps +## + +"""Kraus representation of a quantum channel + +```jldoctest +julia> @superop ℰ; + +julia> @op A₁; @op A₂; @op A₃; + +julia> K = kraus(ℰ, A₁, A₂, A₃); + +julia> @op ρ; + +julia> K*ρ +(A₁ρA₁†+A₂ρA₂†+A₃ρA₃†) +``` +""" +@withmetadata struct KrausRepr <: Symbolic{AbstractSuperOperator} + sop + krausops +end +isexpr(::KrausRepr) = true +iscall(::KrausRepr) = true +arguments(x::KrausRepr) = [sop, x.krausops] +operation(x::KrausRepr) = kraus +head(x::KrausRepr) = :kraus +children(x::KrausRepr) = [:kraus, sop, x.krausops] +kraus(s::Symbolic{AbstractSuperOperator}, xs::Symbolic{AbstractOperator}...) = KrausRepr(s,collect(xs)) +symbollabel(x::KrausRepr) = symbollabel(x.sop) +basis(x::KrausRepr) = basis(x.sop) +Base.show(io::IO, x::KrausRepr) = print(io, symbollabel(x)) \ No newline at end of file diff --git a/src/QSymbolicsBase/repr_CPTP.jl b/src/QSymbolicsBase/repr_CPTP.jl deleted file mode 100644 index f76ec33..0000000 --- a/src/QSymbolicsBase/repr_CPTP.jl +++ /dev/null @@ -1,19 +0,0 @@ -## -# Representations of CPTP maps -## - -@withmetadata struct KrausRepr <: Symbolic{AbstractSuperOperator} - name::Symbol - basis::Basis - krausops -end -isexpr(::KrausRepr) = true -iscall(::KrausRepr) = true -arguments(x::KrausRepr) = x.terms -operation(x::KrausRepr) = kraus -head(x::KrausRepr) = :kraus -children(x::KrausRepr) = [:kraus; x.terms] -kraus(n::Symbol, b::Basis, xs::Symbolic{AbstractOperator}...) = KrausRepr(n,b,collect(xs)) -symbollabel(x::KrausRepr) = x.name -basis(x::KrausRepr) = x.basis -Base.show(io::IO, x::KrausRepr) = print(io, symbollabel(x)) From 49411c974c3b0a94d020a3df888fc995ad5cfec1 Mon Sep 17 00:00:00 2001 From: apkille Date: Tue, 25 Jun 2024 17:59:49 -0400 Subject: [PATCH 05/22] tests --- src/QSymbolicsBase/QSymbolicsBase.jl | 2 +- src/QSymbolicsBase/predefined.jl | 35 +++++----------------------- test/runtests.jl | 1 + test/test_trace.jl | 11 ++++++--- 4 files changed, 16 insertions(+), 33 deletions(-) diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index e8a8b93..fe46613 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -13,7 +13,7 @@ import QuantumInterface: tensor, ⊗, basis,Basis,SpinBasis,FockBasis, nqubits, - projector,dagger,tr,ptrace,vec, + projector,dagger,tr,ptrace, AbstractBra,AbstractKet,AbstractOperator,AbstractSuperOperator export SymQObj,QObj, diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 75bc91a..3aca1bd 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -369,53 +369,30 @@ arguments(x::SPartialTrace) = [x.obj, x.sys] operation(x::SPartialTrace) = ptrace head(x::SPartialTrace) = :ptrace children(x::SPartialTrace) = [:ptrace, x.obj, x.sys] -#basis(x::SPartialTrace) = basis(x.op) Base.show(io::IO, x::SPartialTrace) = print(io, "tr$(x.sys)($(x.obj))") ptrace(x::Symbolic{AbstractOperator}, s) = SPartialTrace(x, s) function ptrace(x::STensorOperator, s) terms = arguments(x) sys_op = terms[s] - tr(sys_op)*STensorOperator(deleteat!(copy(terms), s)) + new_terms = deleteat!(copy(terms), s) + isone(length(new_terms)) ? tr(sys_op)*first(new_terms) : tr(sys_op)*STensorOperator(new_terms) end function ptrace(x::SAddOperator, s) terms = arguments(x) - new_terms = [] + add_terms = [] for i in terms if isexpr(i) && operation(i) === ⊗ isa(i, SScaledOperator) ? prod_terms = arguments(i.obj) : prod_terms = arguments(i) sys_op = prod_terms[s] - push!(new_terms, tr(sys_op)*STensorOperator(deleteat!(copy(prod_terms), s))) + new_terms = deleteat!(copy(prod_terms), s) + isone(length(new_terms)) ? push!(add_terms, tr(sys_op)*first(new_terms)) : push!(add_terms, tr(sys_op)*STensorOperator(new_terms)) else throw(ArgumentError("cannot take partial trace of a single quantum system")) end end - (+)(new_terms...) + (+)(add_terms...) end -"""Vectorization of a symbolic operator - -```jldoctest -julia> @op A; - -julia> vec(A) -|A⟩⟩ -``` -""" -@withmetadata struct SVec <: Symbolic{AbstractKet} - op::Symbolic{AbstractOperator} -end -isexpr(::SVec) = true -iscall(::SVec) = true -arguments(x::SVec) = [x.op] -operation(x::SVec) = vec -head(x::SVec) = :vec -children(x::SVec) = [:vec, x.op] -basis(x::SVec) = basis(x.op) -Base.show(io::IO, x::SVec) = print(io, "|$(x.op)⟩⟩") -vec(x::Symbolic{AbstractOperator}) = SVec(x) -vec(x::SScaled{AbstractOperator}) = x.coeff*vec(x.obj) -vec(x::SAdd{AbstractOperator}) = (+)((vec(i) for i in arguments(x))...) # TODO add vec properties - """Inverse Operator ```jldoctest diff --git a/test/runtests.jl b/test/runtests.jl index bee2e0d..4cb62e3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,6 +35,7 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA @doset "anticommutator" @doset "dagger" @doset "zero_obj" +@doset "trace" VERSION >= v"1.9" && @doset "doctests" get(ENV,"JET_TEST","")=="true" && @doset "jet" diff --git a/test/test_trace.jl b/test/test_trace.jl index eb39720..74191b9 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -6,9 +6,14 @@ using Test @op A; @op B; @op C; @testset "trace tests" begin - @test isequal(tr(2*A), 2*tr(A)) - @test isequal(tr(A+B), tr(A)+tr(B)) + @test_broken isequal(tr(2*A), 2*tr(A)) + @test_broken isequal(tr(A+B), tr(A)+tr(B)) @test isequal(tr(k₁*b₁), b₁*k₁) @test isequal(tr(commutator(A, B)), 0) - @test isequal(tr((⊗)(A, B, C)), tr(A)*tr(B)*tr(C)) + @test_broken isequal(tr((⊗)(A, B, C)), tr(A)*tr(B)*tr(C)) +end + +@testset "partial trace tests" begin + @test isequal(ptrace(A⊗B, 1), tr(A)*B) + @test isequal(ptrace(A⊗B, 2), tr(B)*A) end From e3d2a49352a8d279d5a02ae76925a3ae478c2279 Mon Sep 17 00:00:00 2001 From: apkille Date: Tue, 25 Jun 2024 18:05:34 -0400 Subject: [PATCH 06/22] minor fix --- src/QSymbolicsBase/basic_ops_homogeneous.jl | 6 +++--- src/QSymbolicsBase/predefined.jl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index 648b0de..ad64ec7 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -149,7 +149,7 @@ julia> k₁ ⊗ k₂ julia> @op A; @op B; julia> A ⊗ B -A⊗B +(A⊗B) ``` """ @withmetadata struct STensor{T<:QObj} <: Symbolic{T} @@ -176,9 +176,9 @@ Base.show(io::IO, x::STensorBra) = print(io, join(map(string, arguments(x)),"")) const STensorKet = STensor{AbstractKet} Base.show(io::IO, x::STensorKet) = print(io, join(map(string, arguments(x)),"")) const STensorOperator = STensor{AbstractOperator} -Base.show(io::IO, x::STensorOperator) = print(io, join(map(string, arguments(x)),"⊗")) +Base.show(io::IO, x::STensorOperator) = print(io, "("*join(map(string, arguments(x)),"⊗")*")") const STensorSuperOperator = STensor{AbstractSuperOperator} -Base.show(io::IO, x::STensorSuperOperator) = print(io, join(map(string, arguments(x)),"⊗")) +Base.show(io::IO, x::STensorSuperOperator) = print(io, "("*join(map(string, arguments(x)),"⊗")*")") """Symbolic commutator of two operators diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 3aca1bd..92a40da 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -341,7 +341,7 @@ julia> ptrace(A⊗B, 1) julia> @ket k; @bra b; julia> pure_state = A ⊗ (k*b) -A⊗|k⟩⟨b| +(A⊗|k⟩⟨b|) julia> ptrace(pure_state, 1) (tr(A))|k⟩⟨b| @@ -350,7 +350,7 @@ julia> ptrace(pure_state, 2) (⟨b||k⟩)A julia> mixed_state = (A⊗(k*b)) + ((k*b)⊗B) -(A⊗|k⟩⟨b|+|k⟩⟨b|⊗B) +((A⊗|k⟩⟨b|)+(|k⟩⟨b|⊗B)) julia> ptrace(mixed_state, 1) ((0 + ⟨b||k⟩)B+(tr(A))|k⟩⟨b|) From ac731e2f38a8029f7b2b7380c2a64403fe194228 Mon Sep 17 00:00:00 2001 From: apkille Date: Wed, 26 Jun 2024 10:16:48 -0400 Subject: [PATCH 07/22] another test --- test/test_trace.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_trace.jl b/test/test_trace.jl index 74191b9..575c420 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -16,4 +16,6 @@ end @testset "partial trace tests" begin @test isequal(ptrace(A⊗B, 1), tr(A)*B) @test isequal(ptrace(A⊗B, 2), tr(B)*A) + @test isequal(ptrace((k₁*b₁)⊗A + (k₂*b₂)⊗B, 1), (b₁*k₁)*A + (b₂*k₂)*B) + @test isequal(ptrace((k₁*b₁)⊗A + (k₂*b₂)⊗B, 2), tr(A)*(k₁*b₁) + tr(B)*(k₂*b₂)) end From 5066d34b869d12bea9565b5c11135cbb598d7bbd Mon Sep 17 00:00:00 2001 From: apkille Date: Wed, 26 Jun 2024 10:53:29 -0400 Subject: [PATCH 08/22] hashing for trace and inner product --- src/QSymbolicsBase/basic_ops_inhomogeneous.jl | 1 + src/QSymbolicsBase/predefined.jl | 3 +-- test/test_trace.jl | 11 ++++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl index 681531e..1a8fc66 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -86,6 +86,7 @@ Base.:(*)(b::SZeroBra, k::Symbolic{AbstractKet}) = 0 Base.:(*)(b::Symbolic{AbstractBra}, k::SZeroKet) = 0 Base.:(*)(b::SZeroBra, k::SZeroKet) = 0 Base.show(io::IO, x::SBraKet) = begin print(io,x.bra); print(io,x.ket) end +Base.hash(x::SBraKet, h::UInt) = hash((head(x), arguments(x)), h) Base.isequal(x::SBraKet, y::SBraKet) = isequal(x.bra, y.bra) && isequal(x.ket, y.ket) """Symbolic application of a superoperator on an operator diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 92a40da..0320022 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -286,7 +286,6 @@ function Base.show(io::IO, x::SDagger) print(io,x.obj) print(io,"†") end -symbollabel(x::SDagger) = symbollabel(x.obj) """Trace of an operator @@ -317,7 +316,6 @@ arguments(x::STrace) = [x.op] operation(x::STrace) = tr head(x::STrace) = :tr children(x::STrace) = [:tr, x.op] -basis(x::STrace) = basis(x.op) Base.show(io::IO, x::STrace) = print(io, "tr($(x.op))") tr(x::Symbolic{AbstractOperator}) = STrace(x) tr(x::SScaled{AbstractOperator}) = x.coeff*tr(x.obj) @@ -325,6 +323,7 @@ tr(x::SAdd{AbstractOperator}) = (+)((tr(i) for i in arguments(x))...) tr(x::SOuterKetBra) = x.bra*x.ket tr(x::SCommutator) = 0 tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) # TODO add tr properties +Base.hash(x::STrace, h::UInt) = hash((head(x), arguments(x)), h) Base.isequal(x::STrace, y::STrace) = isequal(x.op, y.op) """Partial trace over system i of a composite quantum system diff --git a/test/test_trace.jl b/test/test_trace.jl index 575c420..1343b89 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -6,16 +6,17 @@ using Test @op A; @op B; @op C; @testset "trace tests" begin - @test_broken isequal(tr(2*A), 2*tr(A)) - @test_broken isequal(tr(A+B), tr(A)+tr(B)) + @test isequal(tr(2*A), 2*tr(A)) + @test isequal(tr(A+B), tr(A)+tr(B)) @test isequal(tr(k₁*b₁), b₁*k₁) @test isequal(tr(commutator(A, B)), 0) - @test_broken isequal(tr((⊗)(A, B, C)), tr(A)*tr(B)*tr(C)) + @test isequal(tr((⊗)(A, B, C)), tr(A)*tr(B)*tr(C)) end @testset "partial trace tests" begin - @test isequal(ptrace(A⊗B, 1), tr(A)*B) - @test isequal(ptrace(A⊗B, 2), tr(B)*A) + @test isequal(ptrace((⊗)(A, B, C), 1), tr(A)*(B⊗C)) + @test isequal(ptrace((⊗)(A, B, C), 2), tr(B)*(A⊗C)) + @test isequal(ptrace((⊗)(A, B, C), 3), tr(C)*(A⊗B)) @test isequal(ptrace((k₁*b₁)⊗A + (k₂*b₂)⊗B, 1), (b₁*k₁)*A + (b₂*k₂)*B) @test isequal(ptrace((k₁*b₁)⊗A + (k₂*b₂)⊗B, 2), tr(A)*(k₁*b₁) + tr(B)*(k₂*b₂)) end From 1e3c95630f41584892836e9019a4855286d4c906 Mon Sep 17 00:00:00 2001 From: apkille Date: Wed, 26 Jun 2024 11:02:56 -0400 Subject: [PATCH 09/22] printing fix --- src/QSymbolicsBase/basic_ops_homogeneous.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index ad64ec7..d4df9bf 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -45,7 +45,7 @@ function Base.show(io::IO, x::SScaledKet) end const SScaledOperator = SScaled{AbstractOperator} function Base.show(io::IO, x::SScaledOperator) - if x.coeff isa Number + if x.coeff isa Real print(io, "$(x.coeff)$(x.obj)") else print(io, "($(x.coeff))$(x.obj)") From 4793dc7d4376498fde5f1c523f8a41215518a4fb Mon Sep 17 00:00:00 2001 From: apkille Date: Wed, 26 Jun 2024 13:05:40 -0400 Subject: [PATCH 10/22] sorted_arguments for trace --- src/QSymbolicsBase/QSymbolicsBase.jl | 2 +- src/QSymbolicsBase/predefined.jl | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index fe46613..3dd94a0 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -1,7 +1,7 @@ using Symbolics import Symbolics: simplify using SymbolicUtils -import SymbolicUtils: Symbolic,_isone,flatten_term,isnotflat,Chain,Fixpoint +import SymbolicUtils: Symbolic,_isone,flatten_term,isnotflat,Chain,Fixpoint,sorted_arguments using TermInterface import TermInterface: isexpr,head,iscall,children,operation,arguments,metadata diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 0320022..af0fe3e 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -313,6 +313,7 @@ end isexpr(::STrace) = true iscall(::STrace) = true arguments(x::STrace) = [x.op] +sorted_arguments(x::STrace) = arguments(x) operation(x::STrace) = tr head(x::STrace) = :tr children(x::STrace) = [:tr, x.op] @@ -322,7 +323,7 @@ tr(x::SScaled{AbstractOperator}) = x.coeff*tr(x.obj) tr(x::SAdd{AbstractOperator}) = (+)((tr(i) for i in arguments(x))...) tr(x::SOuterKetBra) = x.bra*x.ket tr(x::SCommutator) = 0 -tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) # TODO add tr properties +tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) Base.hash(x::STrace, h::UInt) = hash((head(x), arguments(x)), h) Base.isequal(x::STrace, y::STrace) = isequal(x.op, y.op) From b7aeab12e181798f2283b9f89b5c2da6d611a8a9 Mon Sep 17 00:00:00 2001 From: apkille Date: Wed, 26 Jun 2024 13:40:24 -0400 Subject: [PATCH 11/22] superop tests --- src/QSymbolicsBase/basic_ops_inhomogeneous.jl | 3 ++- test/test_superop.jl | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl index 1a8fc66..ce00cce 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -110,8 +110,9 @@ children(x::SSuperOpApply) = [:*,x.sop,x.op] Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::Symbolic{AbstractOperator}) = SSuperOpApply(sop,op) Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::SZeroOperator) = SZeroOperator() Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::Symbolic{AbstractKet}) = SSuperOpApply(sop,SProjector(k)) -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::SZeroKet) = SZeroKet() +Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::SZeroKet) = SZeroOperator() Base.:(*)(sop::KrausRepr, op::Symbolic{AbstractOperator}) = (+)((i*op*dagger(i) for i in sop.krausops)...) +Base.:(*)(sop::KrausRepr, k::Symbolic{AbstractKet}) = (+)((i*SProjector(k)*dagger(i) for i in sop.krausops)...) Base.show(io::IO, x::SSuperOpApply) = print(io, "$(x.sop)[$(x.op)]") basis(x::SSuperOpApply) = basis(x.op) diff --git a/test/test_superop.jl b/test/test_superop.jl index 07e4b20..73a9831 100644 --- a/test/test_superop.jl +++ b/test/test_superop.jl @@ -44,6 +44,19 @@ noisy_pair_fromdm = (noiseop ⊗ noiseop) * pure_pair_dm @test tr(express(noisy_pair)) ≈ tr(express(noisy_pair_fromdm)) ≈ 1 @test express(noisy_pair) ≈ express(noisy_pair_fromdm) +@op A; @op B; @op C; @op O; @ket k; +@superop S; K = kraus(S, A, B, C); + + + +@testset "symbolic superoperator tests" begin + @test isequal(S*SZeroOperator(), SZeroOperator()) + @test isequal(S*SZeroKet(), SZeroOperator()) + @test isequal(S*k, S*projector(k)) + @test isequal(K*O, A*O*dagger(A) + B*O*dagger(B) + C*O*dagger(C)) + @test isequal(K*k, A*projector(k)*dagger(A) + B*projector(k)*dagger(B) + C*projector(k)*dagger(C)) +end + # TODO # test against depolarization # Depolarization over two qubits is different from depolarizing each separately (see related tutorial) From 871b1c33a7011d07e8e714f10735c3d5fdb9f36e Mon Sep 17 00:00:00 2001 From: apkille Date: Wed, 26 Jun 2024 13:47:29 -0400 Subject: [PATCH 12/22] change doctest --- src/QSymbolicsBase/predefined.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index af0fe3e..dc04711 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -295,9 +295,6 @@ julia> @op A; @op B; julia> tr(A) tr(A) -julia> tr(A⊗B) -tr(A)*tr(B) - julia> tr(commutator(A, B)) 0 From 343108b7cd20e16fec853e2bb6ca2f9dc093a442 Mon Sep 17 00:00:00 2001 From: apkille Date: Thu, 27 Jun 2024 13:56:57 -0400 Subject: [PATCH 13/22] JET fixes --- src/QSymbolicsBase/predefined_CPTP.jl | 4 ++-- src/QSymbolicsBase/rules.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/QSymbolicsBase/predefined_CPTP.jl b/src/QSymbolicsBase/predefined_CPTP.jl index c6415c5..255041d 100644 --- a/src/QSymbolicsBase/predefined_CPTP.jl +++ b/src/QSymbolicsBase/predefined_CPTP.jl @@ -73,10 +73,10 @@ julia> K*ρ end isexpr(::KrausRepr) = true iscall(::KrausRepr) = true -arguments(x::KrausRepr) = [sop, x.krausops] +arguments(x::KrausRepr) = [x.sop, x.krausops] operation(x::KrausRepr) = kraus head(x::KrausRepr) = :kraus -children(x::KrausRepr) = [:kraus, sop, x.krausops] +children(x::KrausRepr) = [:kraus, x.sop, x.krausops] kraus(s::Symbolic{AbstractSuperOperator}, xs::Symbolic{AbstractOperator}...) = KrausRepr(s,collect(xs)) symbollabel(x::KrausRepr) = symbollabel(x.sop) basis(x::KrausRepr) = basis(x.sop) diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index df747c4..ea121e4 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -63,7 +63,7 @@ end # Flattening expressions RULES_FLATTEN = [ @rule(~x::isnotflat_precheck(⊗) => flatten_term(⊗, ~x)), - @rule ⊗(~~xs::hasscalings) => prefactorscalings_rule(xs) + @rule ⊗(~~xs::hasscalings) => prefactorscalings_rule(~~xs) ] # Pauli identities From 7c25134bd12f463392c3a3405f3b8bf24c9eb2ee Mon Sep 17 00:00:00 2001 From: apkille Date: Fri, 28 Jun 2024 21:03:28 -0400 Subject: [PATCH 14/22] cleanup --- .typos.toml | 3 +- src/QSymbolicsBase/QSymbolicsBase.jl | 32 +-- src/QSymbolicsBase/basic_ops_homogeneous.jl | 14 +- src/QSymbolicsBase/basic_ops_inhomogeneous.jl | 27 -- src/QSymbolicsBase/basic_superops.jl | 64 +++++ src/QSymbolicsBase/linalg.jl | 236 ++++++++++++++++++ src/QSymbolicsBase/predefined.jl | 223 +---------------- src/QSymbolicsBase/predefined_CPTP.jl | 36 +-- src/QSymbolicsBase/rules.jl | 54 +--- src/QSymbolicsBase/utils.jl | 46 ++++ 10 files changed, 372 insertions(+), 363 deletions(-) create mode 100644 src/QSymbolicsBase/basic_superops.jl create mode 100644 src/QSymbolicsBase/linalg.jl create mode 100644 src/QSymbolicsBase/utils.jl diff --git a/.typos.toml b/.typos.toml index 7364886..3e4e58a 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,2 +1,3 @@ [default.extend-words] -ket = "ket" \ No newline at end of file +ket = "ket" +BA = "BA" \ No newline at end of file diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index 3dd94a0..df3a039 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -46,27 +46,6 @@ export SymQObj,QObj, KrausRepr, kraus -function countmap(samples) # A simpler version of StatsBase.countmap, because StatsBase is slow to import - counts = Dict{Any,Any}() - for s in samples - counts[s] = get(counts, s, 0)+1 - end - counts -end - -function countmap_flatten(samples, flattenhead) - counts = Dict{Any,Any}() - for s in samples - if isexpr(s) && s isa flattenhead # TODO Could you use the TermInterface `operation` here instead of `flattenhead`? - coef, term = arguments(s) - counts[term] = get(counts, term, 0)+coef - else - counts[s] = get(counts, s, 0)+1 - end - end - counts -end - ## # Metadata cache helpers ## @@ -168,15 +147,24 @@ Base.isequal(::Symbolic{Complex}, ::SymQObj) = false # use a macro to provide specializations if that is indeed the case propsequal(x,y) = all(n->isequal(getproperty(x,n),getproperty(y,n)), propertynames(x)) + +## +# Utilities +## + +include("utils.jl") + ## # Most symbolic objects defined here ## include("literal_objects.jl") -include("predefined_CPTP.jl") include("basic_ops_homogeneous.jl") include("basic_ops_inhomogeneous.jl") +include("basic_superops.jl") +include("linalg.jl") include("predefined.jl") +include("predefined_CPTP.jl") ## # Symbolic and simplification rules diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index d4df9bf..4f86664 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -75,8 +75,8 @@ julia> k₁ + k₂ _arguments_precomputed end function SAdd{S}(d) where S - xs = [c*obj for (c,obj) in d] - length(d)==1 ? first(xs) : SAdd{S}(d,Set(xs),xs) + terms = flattenop(+,[c*obj for (c,obj) in d]) + length(d)==1 ? first(xs) : SAdd{S}(d,Set(terms),terms) end isexpr(::SAdd) = true iscall(::SAdd) = true @@ -121,8 +121,8 @@ AB @withmetadata struct SMulOperator <: Symbolic{AbstractOperator} terms function SMulOperator(terms) - coeff, cleanterms = prefactorscalings(terms, scalar=true) - coeff*new(cleanterms) + coeff, cleanterms = prefactorscalings(terms) + coeff*new(flattenop(*,cleanterms)) end end isexpr(::SMulOperator) = true @@ -156,7 +156,7 @@ julia> A ⊗ B terms function STensor{S}(terms) where S coeff, cleanterms = prefactorscalings(terms) - coeff * new{S}(cleanterms) + coeff * new{S}(flattenop(⊗,cleanterms)) end end isexpr(::STensor) = true @@ -199,7 +199,7 @@ julia> commutator(A, A) op1 op2 function SCommutator(o1, o2) - coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) + coeff, cleanterms = prefactorscalings([o1 o2]) cleanterms[1] === cleanterms[2] ? SZeroOperator() : coeff*new(cleanterms...) end end @@ -233,7 +233,7 @@ julia> expand(anticommutator(A, B)) op1 op2 function SAnticommutator(o1, o2) - coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) + coeff, cleanterms = prefactorscalings([o1 o2]) coeff*new(cleanterms...) end end diff --git a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl index ce00cce..8c8723b 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -89,33 +89,6 @@ Base.show(io::IO, x::SBraKet) = begin print(io,x.bra); print(io,x.ket) end Base.hash(x::SBraKet, h::UInt) = hash((head(x), arguments(x)), h) Base.isequal(x::SBraKet, y::SBraKet) = isequal(x.bra, y.bra) && isequal(x.ket, y.ket) -"""Symbolic application of a superoperator on an operator - -```jldoctest -julia> @op A; @superop S; - -julia> S*A -S[A] -""" -@withmetadata struct SSuperOpApply <: Symbolic{AbstractOperator} - sop - op -end -isexpr(::SSuperOpApply) = true -iscall(::SSuperOpApply) = true -arguments(x::SSuperOpApply) = [x.sop,x.op] -operation(x::SSuperOpApply) = * -head(x::SSuperOpApply) = :* -children(x::SSuperOpApply) = [:*,x.sop,x.op] -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::Symbolic{AbstractOperator}) = SSuperOpApply(sop,op) -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::SZeroOperator) = SZeroOperator() -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::Symbolic{AbstractKet}) = SSuperOpApply(sop,SProjector(k)) -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::SZeroKet) = SZeroOperator() -Base.:(*)(sop::KrausRepr, op::Symbolic{AbstractOperator}) = (+)((i*op*dagger(i) for i in sop.krausops)...) -Base.:(*)(sop::KrausRepr, k::Symbolic{AbstractKet}) = (+)((i*SProjector(k)*dagger(i) for i in sop.krausops)...) -Base.show(io::IO, x::SSuperOpApply) = print(io, "$(x.sop)[$(x.op)]") -basis(x::SSuperOpApply) = basis(x.op) - """Symbolic outer product of a ket and a bra ```jldoctest julia> @bra b; @ket k; diff --git a/src/QSymbolicsBase/basic_superops.jl b/src/QSymbolicsBase/basic_superops.jl new file mode 100644 index 0000000..36746b6 --- /dev/null +++ b/src/QSymbolicsBase/basic_superops.jl @@ -0,0 +1,64 @@ +## +# Superoperator representations +## + +"""Kraus representation of a quantum channel + +```jldoctest +julia> @superop ℰ; + +julia> @op A₁; @op A₂; @op A₃; + +julia> K = kraus(ℰ, A₁, A₂, A₃); + +julia> @op ρ; + +julia> K*ρ +(A₁ρA₁†+A₂ρA₂†+A₃ρA₃†) +``` +""" +@withmetadata struct KrausRepr <: Symbolic{AbstractSuperOperator} + sop + krausops +end +isexpr(::KrausRepr) = true +iscall(::KrausRepr) = true +arguments(x::KrausRepr) = [x.sop, x.krausops] +operation(x::KrausRepr) = kraus +head(x::KrausRepr) = :kraus +children(x::KrausRepr) = [:kraus, x.sop, x.krausops] +kraus(s::Symbolic{AbstractSuperOperator}, xs::Symbolic{AbstractOperator}...) = KrausRepr(s,collect(xs)) +symbollabel(x::KrausRepr) = symbollabel(x.sop) +basis(x::KrausRepr) = basis(x.sop) +Base.show(io::IO, x::KrausRepr) = print(io, symbollabel(x)) + +## +# Superoperator operations +## + +"""Symbolic application of a superoperator on an operator + +```jldoctest +julia> @op A; @superop S; + +julia> S*A +S[A] +""" +@withmetadata struct SSuperOpApply <: Symbolic{AbstractOperator} + sop + op +end +isexpr(::SSuperOpApply) = true +iscall(::SSuperOpApply) = true +arguments(x::SSuperOpApply) = [x.sop,x.op] +operation(x::SSuperOpApply) = * +head(x::SSuperOpApply) = :* +children(x::SSuperOpApply) = [:*,x.sop,x.op] +Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::Symbolic{AbstractOperator}) = SSuperOpApply(sop,op) +Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::SZeroOperator) = SZeroOperator() +Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::Symbolic{AbstractKet}) = SSuperOpApply(sop,SProjector(k)) +Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::SZeroKet) = SZeroOperator() +Base.:(*)(sop::KrausRepr, op::Symbolic{AbstractOperator}) = (+)((i*op*dagger(i) for i in sop.krausops)...) +Base.:(*)(sop::KrausRepr, k::Symbolic{AbstractKet}) = (+)((i*SProjector(k)*dagger(i) for i in sop.krausops)...) +Base.show(io::IO, x::SSuperOpApply) = print(io, "$(x.sop)[$(x.op)]") +basis(x::SSuperOpApply) = basis(x.op) \ No newline at end of file diff --git a/src/QSymbolicsBase/linalg.jl b/src/QSymbolicsBase/linalg.jl new file mode 100644 index 0000000..4c82b85 --- /dev/null +++ b/src/QSymbolicsBase/linalg.jl @@ -0,0 +1,236 @@ +## +# Linear algebra operations on quantum objects. +## + +"""Projector for a given ket + +```jldoctest +julia> SProjector(X1⊗X2) +𝐏[|X₁⟩|X₂⟩] + +julia> express(SProjector(X2)) +Operator(dim=2x2) + basis: Spin(1/2) + 0.5+0.0im -0.5-0.0im + -0.5+0.0im 0.5+0.0im +```""" +@withmetadata struct SProjector <: Symbolic{AbstractOperator} + ket::Symbolic{AbstractKet} # TODO parameterize +end +isexpr(::SProjector) = true +iscall(::SProjector) = true +arguments(x::SProjector) = [x.ket] +operation(x::SProjector) = projector +head(x::SProjector) = :projector +children(x::SProjector) = [:projector,x.ket] +projector(x::Symbolic{AbstractKet}) = SProjector(x) +projector(x::SZeroKet) = SZeroOperator() +basis(x::SProjector) = basis(x.ket) +function Base.show(io::IO, x::SProjector) + print(io,"𝐏[") + print(io,x.ket) + print(io,"]") +end + +"""Dagger, i.e., adjoint of quantum objects (kets, bras, operators) + +```jldoctest +julia> @ket a; @op A; + +julia> dagger(2*im*A*a) +0 - 2im|a⟩†A† + +julia> @op B; + +julia> dagger(A*B) +B†A† + +julia> ℋ = SHermitianOperator(:ℋ); U = SUnitaryOperator(:U); + +julia> dagger(ℋ) +ℋ + +julia> dagger(U) +U⁻¹ +``` +""" +@withmetadata struct SDagger{T<:QObj} <: Symbolic{T} + obj +end +isexpr(::SDagger) = true +iscall(::SDagger) = true +arguments(x::SDagger) = [x.obj] +operation(x::SDagger) = dagger +head(x::SDagger) = :dagger +children(x::SDagger) = [:dagger, x.obj] +dagger(x::Symbolic{AbstractBra}) = SDagger{AbstractKet}(x) +dagger(x::Symbolic{AbstractKet}) = SDagger{AbstractBra}(x) +dagger(x::Symbolic{AbstractOperator}) = SDagger{AbstractOperator}(x) +dagger(x::SScaledKet) = SScaledBra(conj(x.coeff), dagger(x.obj)) +dagger(x::SAdd) = (+)((dagger(i) for i in arguments(x))...) +dagger(x::SScaledBra) = SScaledKet(conj(x.coeff), dagger(x.obj)) +dagger(x::SZeroOperator) = x +dagger(x::SHermitianOperator) = x +dagger(x::SHermitianUnitaryOperator) = x +dagger(x::SUnitaryOperator) = inv(x) +dagger(x::STensorBra) = STensorKet(collect(dagger(i) for i in x.terms)) +dagger(x::STensorKet) = STensorBra(collect(dagger(i) for i in x.terms)) +dagger(x::STensorOperator) = STensorOperator(collect(dagger(i) for i in x.terms)) +dagger(x::SScaledOperator) = SScaledOperator(conj(x.coeff), dagger(x.obj)) +dagger(x::SApplyKet) = dagger(x.ket)*dagger(x.op) +dagger(x::SApplyBra) = dagger(x.op)*dagger(x.bra) +dagger(x::SMulOperator) = SMulOperator(collect(dagger(i) for i in reverse(x.terms))) +dagger(x::SBraKet) = SBraKet(dagger(x.ket), dagger(x.bra)) +dagger(x::SOuterKetBra) = SOuterKetBra(dagger(x.bra), dagger(x.ket)) +dagger(x::SDagger) = x.obj +basis(x::SDagger) = basis(x.obj) +function Base.show(io::IO, x::SDagger) + print(io,x.obj) + print(io,"†") +end + +"""Trace of an operator + +```jldoctest +julia> @op A; @op B; + +julia> tr(A) +tr(A) + +julia> tr(commutator(A, B)) +0 + +julia> @bra b; @ket k; + +julia> tr(k*b) +⟨b||k⟩ +``` +""" +@withmetadata struct STrace <: Symbolic{Complex} + op::Symbolic{AbstractOperator} +end +isexpr(::STrace) = true +iscall(::STrace) = true +arguments(x::STrace) = [x.op] +sorted_arguments(x::STrace) = arguments(x) +operation(x::STrace) = tr +head(x::STrace) = :tr +children(x::STrace) = [:tr, x.op] +Base.show(io::IO, x::STrace) = print(io, "tr($(x.op))") +tr(x::Symbolic{AbstractOperator}) = STrace(x) +tr(x::SScaled{AbstractOperator}) = x.coeff*tr(x.obj) +tr(x::SAdd{AbstractOperator}) = (+)((tr(i) for i in arguments(x))...) +tr(x::SOuterKetBra) = x.bra*x.ket +tr(x::SCommutator) = 0 +tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) +Base.hash(x::STrace, h::UInt) = hash((head(x), arguments(x)), h) +Base.isequal(x::STrace, y::STrace) = isequal(x.op, y.op) + +"""Partial trace over system i of a composite quantum system + +```jldoctest +julia> @op A; @op B; + +julia> ptrace(A, 1) +tr1(A) + +julia> ptrace(A⊗B, 1) +(tr(A))B + +julia> @ket k; @bra b; + +julia> pure_state = A ⊗ (k*b) +(A⊗|k⟩⟨b|) + +julia> ptrace(pure_state, 1) +(tr(A))|k⟩⟨b| + +julia> ptrace(pure_state, 2) +(⟨b||k⟩)A + +julia> mixed_state = (A⊗(k*b)) + ((k*b)⊗B) +((A⊗|k⟩⟨b|)+(|k⟩⟨b|⊗B)) + +julia> ptrace(mixed_state, 1) +((0 + ⟨b||k⟩)B+(tr(A))|k⟩⟨b|) + +julia> ptrace(mixed_state, 2) +((0 + ⟨b||k⟩)A+(tr(B))|k⟩⟨b|) +``` +""" +@withmetadata struct SPartialTrace <: Symbolic{AbstractOperator} + obj + sys::Int +end +isexpr(::SPartialTrace) = true +iscall(::SPartialTrace) = true +arguments(x::SPartialTrace) = [x.obj, x.sys] +operation(x::SPartialTrace) = ptrace +head(x::SPartialTrace) = :ptrace +children(x::SPartialTrace) = [:ptrace, x.obj, x.sys] +function basis(x::SPartialTrace) + obj_bases = collect(basis(x.obj).bases) + new_bases = deleteat!(copy(obj_bases), x.sys) + tensor(new_bases...) +end +Base.show(io::IO, x::SPartialTrace) = print(io, "tr$(x.sys)($(x.obj))") +function ptrace(x::Symbolic{AbstractOperator}, s) + if isa(basis(x), QuantumInterface.CompositeBasis) + SPartialTrace(x, s) + else + throw(ArgumentError("cannot take partial trace of a single quantum system")) + end +end +function ptrace(x::STensorOperator, s) + terms = arguments(x) + newterms = [] + if isa(basis(terms[s]), QuantumInterface.CompositeBasis) + SPartial(x, s) + else + sys_op = terms[s] + new_terms = deleteat!(copy(terms), s) + isone(length(new_terms)) ? tr(sys_op)*first(new_terms) : tr(sys_op)*STensorOperator(new_terms) + end +end +function ptrace(x::SAddOperator, s) + terms = arguments(x) + add_terms = [] + for i in terms + if isexpr(i) && operation(i) === ⊗ + isa(i, SScaledOperator) ? prod_terms = arguments(i.obj) : prod_terms = arguments(i) + sys_op = prod_terms[s] + new_terms = deleteat!(copy(prod_terms), s) + isone(length(new_terms)) ? push!(add_terms, tr(sys_op)*first(new_terms)) : push!(add_terms, tr(sys_op)*STensorOperator(new_terms)) + else + throw(ArgumentError("cannot take partial trace of a single quantum system")) + end + end + (+)(add_terms...) +end + +"""Inverse Operator + +```jldoctest +julia> @op A; + +julia> inv(A) +A⁻¹ + +julia> inv(A)*A +𝕀 +``` +""" +@withmetadata struct SInvOperator <: Symbolic{AbstractOperator} + op::Symbolic{AbstractOperator} +end +isexpr(::SInvOperator) = true +iscall(::SInvOperator) = true +arguments(x::SInvOperator) = [x.op] +operation(x::SInvOperator) = inv +head(x::SInvOperator) = :inv +children(x::SInvOperator) = [:inv, x.op] +basis(x::SInvOperator) = basis(x.op) +Base.show(io::IO, x::SInvOperator) = print(io, "$(x.op)⁻¹") +Base.:(*)(invop::SInvOperator, op::SOperator) = isequal(invop.op, op) ? IdentityOp(basis(op)) : SMulOperator(invop, op) +Base.:(*)(op::SOperator, invop::SInvOperator) = isequal(op, invop.op) ? IdentityOp(basis(op)) : SMulOperator(op, invop) +inv(x::Symbolic{AbstractOperator}) = SInvOperator(x) \ No newline at end of file diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index dc04711..7e599cc 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -200,223 +200,6 @@ const Destroy = const â = DestroyOp() # Other special or useful objects ## -"""Projector for a given ket - -```jldoctest -julia> SProjector(X1⊗X2) -𝐏[|X₁⟩|X₂⟩] - -julia> express(SProjector(X2)) -Operator(dim=2x2) - basis: Spin(1/2) - 0.5+0.0im -0.5-0.0im - -0.5+0.0im 0.5+0.0im -```""" -@withmetadata struct SProjector <: Symbolic{AbstractOperator} - ket::Symbolic{AbstractKet} # TODO parameterize -end -isexpr(::SProjector) = true -iscall(::SProjector) = true -arguments(x::SProjector) = [x.ket] -operation(x::SProjector) = projector -head(x::SProjector) = :projector -children(x::SProjector) = [:projector,x.ket] -projector(x::Symbolic{AbstractKet}) = SProjector(x) -projector(x::SZeroKet) = SZeroOperator() -basis(x::SProjector) = basis(x.ket) -function Base.show(io::IO, x::SProjector) - print(io,"𝐏[") - print(io,x.ket) - print(io,"]") -end - -"""Dagger, i.e., adjoint of quantum objects (kets, bras, operators) - -```jldoctest -julia> @ket a; @op A; - -julia> dagger(2*im*A*a) -0 - 2im|a⟩†A† - -julia> @op B; - -julia> dagger(A*B) -B†A† - -julia> ℋ = SHermitianOperator(:ℋ); U = SUnitaryOperator(:U); - -julia> dagger(ℋ) -ℋ - -julia> dagger(U) -U⁻¹ -``` -""" -@withmetadata struct SDagger{T<:QObj} <: Symbolic{T} - obj -end -isexpr(::SDagger) = true -iscall(::SDagger) = true -arguments(x::SDagger) = [x.obj] -operation(x::SDagger) = dagger -head(x::SDagger) = :dagger -children(x::SDagger) = [:dagger, x.obj] -dagger(x::Symbolic{AbstractBra}) = SDagger{AbstractKet}(x) -dagger(x::Symbolic{AbstractKet}) = SDagger{AbstractBra}(x) -dagger(x::Symbolic{AbstractOperator}) = SDagger{AbstractOperator}(x) -dagger(x::SScaledKet) = SScaledBra(conj(x.coeff), dagger(x.obj)) -dagger(x::SAdd) = (+)((dagger(i) for i in arguments(x))...) -dagger(x::SScaledBra) = SScaledKet(conj(x.coeff), dagger(x.obj)) -dagger(x::SZeroOperator) = x -dagger(x::SHermitianOperator) = x -dagger(x::SHermitianUnitaryOperator) = x -dagger(x::SUnitaryOperator) = inv(x) -dagger(x::STensorBra) = STensorKet(collect(dagger(i) for i in x.terms)) -dagger(x::STensorKet) = STensorBra(collect(dagger(i) for i in x.terms)) -dagger(x::STensorOperator) = STensorOperator(collect(dagger(i) for i in x.terms)) -dagger(x::SScaledOperator) = SScaledOperator(conj(x.coeff), dagger(x.obj)) -dagger(x::SApplyKet) = dagger(x.ket)*dagger(x.op) -dagger(x::SApplyBra) = dagger(x.op)*dagger(x.bra) -dagger(x::SMulOperator) = SMulOperator(collect(dagger(i) for i in reverse(x.terms))) -dagger(x::SBraKet) = SBraKet(dagger(x.ket), dagger(x.bra)) -dagger(x::SOuterKetBra) = SOuterKetBra(dagger(x.bra), dagger(x.ket)) -dagger(x::SDagger) = x.obj -basis(x::SDagger) = basis(x.obj) -function Base.show(io::IO, x::SDagger) - print(io,x.obj) - print(io,"†") -end - -"""Trace of an operator - -```jldoctest -julia> @op A; @op B; - -julia> tr(A) -tr(A) - -julia> tr(commutator(A, B)) -0 - -julia> @bra b; @ket k; - -julia> tr(k*b) -⟨b||k⟩ -``` -""" -@withmetadata struct STrace <: Symbolic{Complex} - op::Symbolic{AbstractOperator} -end -isexpr(::STrace) = true -iscall(::STrace) = true -arguments(x::STrace) = [x.op] -sorted_arguments(x::STrace) = arguments(x) -operation(x::STrace) = tr -head(x::STrace) = :tr -children(x::STrace) = [:tr, x.op] -Base.show(io::IO, x::STrace) = print(io, "tr($(x.op))") -tr(x::Symbolic{AbstractOperator}) = STrace(x) -tr(x::SScaled{AbstractOperator}) = x.coeff*tr(x.obj) -tr(x::SAdd{AbstractOperator}) = (+)((tr(i) for i in arguments(x))...) -tr(x::SOuterKetBra) = x.bra*x.ket -tr(x::SCommutator) = 0 -tr(x::STensorOperator) = (*)((tr(i) for i in arguments(x))...) -Base.hash(x::STrace, h::UInt) = hash((head(x), arguments(x)), h) -Base.isequal(x::STrace, y::STrace) = isequal(x.op, y.op) - -"""Partial trace over system i of a composite quantum system - -```jldoctest -julia> @op A; @op B; - -julia> ptrace(A, 1) -tr1(A) - -julia> ptrace(A⊗B, 1) -(tr(A))B - -julia> @ket k; @bra b; - -julia> pure_state = A ⊗ (k*b) -(A⊗|k⟩⟨b|) - -julia> ptrace(pure_state, 1) -(tr(A))|k⟩⟨b| - -julia> ptrace(pure_state, 2) -(⟨b||k⟩)A - -julia> mixed_state = (A⊗(k*b)) + ((k*b)⊗B) -((A⊗|k⟩⟨b|)+(|k⟩⟨b|⊗B)) - -julia> ptrace(mixed_state, 1) -((0 + ⟨b||k⟩)B+(tr(A))|k⟩⟨b|) - -julia> ptrace(mixed_state, 2) -((0 + ⟨b||k⟩)A+(tr(B))|k⟩⟨b|) -``` -""" -@withmetadata struct SPartialTrace <: Symbolic{Complex} - obj - sys::Int -end -isexpr(::SPartialTrace) = true -iscall(::SPartialTrace) = true -arguments(x::SPartialTrace) = [x.obj, x.sys] -operation(x::SPartialTrace) = ptrace -head(x::SPartialTrace) = :ptrace -children(x::SPartialTrace) = [:ptrace, x.obj, x.sys] -Base.show(io::IO, x::SPartialTrace) = print(io, "tr$(x.sys)($(x.obj))") -ptrace(x::Symbolic{AbstractOperator}, s) = SPartialTrace(x, s) -function ptrace(x::STensorOperator, s) - terms = arguments(x) - sys_op = terms[s] - new_terms = deleteat!(copy(terms), s) - isone(length(new_terms)) ? tr(sys_op)*first(new_terms) : tr(sys_op)*STensorOperator(new_terms) -end -function ptrace(x::SAddOperator, s) - terms = arguments(x) - add_terms = [] - for i in terms - if isexpr(i) && operation(i) === ⊗ - isa(i, SScaledOperator) ? prod_terms = arguments(i.obj) : prod_terms = arguments(i) - sys_op = prod_terms[s] - new_terms = deleteat!(copy(prod_terms), s) - isone(length(new_terms)) ? push!(add_terms, tr(sys_op)*first(new_terms)) : push!(add_terms, tr(sys_op)*STensorOperator(new_terms)) - else - throw(ArgumentError("cannot take partial trace of a single quantum system")) - end - end - (+)(add_terms...) -end - -"""Inverse Operator - -```jldoctest -julia> @op A; - -julia> inv(A) -A⁻¹ - -julia> inv(A)*A -𝕀 -``` -""" -@withmetadata struct SInvOperator <: Symbolic{AbstractOperator} - op::Symbolic{AbstractOperator} -end -isexpr(::SInvOperator) = true -iscall(::SInvOperator) = true -arguments(x::SInvOperator) = [x.op] -operation(x::SInvOperator) = inv -head(x::SInvOperator) = :inv -children(x::SInvOperator) = [:inv, x.op] -basis(x::SInvOperator) = basis(x.op) -Base.show(io::IO, x::SInvOperator) = print(io, "$(x.op)⁻¹") -Base.:(*)(invop::SInvOperator, op::SOperator) = isequal(invop.op, op) ? IdentityOp(basis(op)) : SMulOperator(invop, op) -Base.:(*)(op::SOperator, invop::SInvOperator) = isequal(op, invop.op) ? IdentityOp(basis(op)) : SMulOperator(op, invop) -inv(x::Symbolic{AbstractOperator}) = SInvOperator(x) - """Completely depolarized state ```jldoctest @@ -438,7 +221,8 @@ julia> express(MixedState(X1⊗X2), CliffordRepr()) 𝒵ₗ━━ + Z_ + _Z -```""" +``` +""" @withmetadata struct MixedState <: Symbolic{AbstractOperator} basis::Basis # From QuantumOpticsBase # TODO make QuantumInterface end @@ -457,7 +241,8 @@ julia> IdentityOp(X1⊗X2) julia> express(IdentityOp(Z2)) Operator(dim=2x2) basis: Spin(1/2)sparse([1, 2], [1, 2], ComplexF64[1.0 + 0.0im, 1.0 + 0.0im], 2, 2) -```""" +``` +""" @withmetadata struct IdentityOp <: Symbolic{AbstractOperator} basis::Basis # From QuantumOpticsBase # TODO make QuantumInterface end diff --git a/src/QSymbolicsBase/predefined_CPTP.jl b/src/QSymbolicsBase/predefined_CPTP.jl index 255041d..ea6c766 100644 --- a/src/QSymbolicsBase/predefined_CPTP.jl +++ b/src/QSymbolicsBase/predefined_CPTP.jl @@ -46,38 +46,4 @@ function Base.show(io::IO, x::GateCPTP) print(io, "[") print(io, x.gate) print(io, "]") -end - -## -# Representations of CPTP maps -## - -"""Kraus representation of a quantum channel - -```jldoctest -julia> @superop ℰ; - -julia> @op A₁; @op A₂; @op A₃; - -julia> K = kraus(ℰ, A₁, A₂, A₃); - -julia> @op ρ; - -julia> K*ρ -(A₁ρA₁†+A₂ρA₂†+A₃ρA₃†) -``` -""" -@withmetadata struct KrausRepr <: Symbolic{AbstractSuperOperator} - sop - krausops -end -isexpr(::KrausRepr) = true -iscall(::KrausRepr) = true -arguments(x::KrausRepr) = [x.sop, x.krausops] -operation(x::KrausRepr) = kraus -head(x::KrausRepr) = :kraus -children(x::KrausRepr) = [:kraus, x.sop, x.krausops] -kraus(s::Symbolic{AbstractSuperOperator}, xs::Symbolic{AbstractOperator}...) = KrausRepr(s,collect(xs)) -symbollabel(x::KrausRepr) = symbollabel(x.sop) -basis(x::KrausRepr) = basis(x.sop) -Base.show(io::IO, x::KrausRepr) = print(io, symbollabel(x)) \ No newline at end of file +end \ No newline at end of file diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index ea121e4..52de644 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -1,5 +1,5 @@ ## -# This file defines automatic simplification rules for specific operations of quantum objects. +# This file defines manual simplification rules for specific operations of quantum objects. ## ## @@ -12,60 +12,10 @@ function hasscalings(xs) end _isa(T) = x->isa(x,T) -## -# Determining factors for expressions containing quantum objects -## - -function prefactorscalings(xs; scalar=false) # If the scalar keyword is true, then only scalar factors will be returned as coefficients - terms = [] - coeff = 1::Any - for x in xs - if isexpr(x) && operation(x) == * - c,t = arguments(x) - if !scalar && !isa(x,SOuterKetBra) - coeff *= c - push!(terms,t) - elseif scalar && c isa Number - coeff *= c - push!(terms, t) - else - push!(terms,(*)(arguments(x)...)) - end - else - push!(terms,x) - end - end - coeff, terms -end - -function prefactorscalings_rule(xs) - coeff, terms = prefactorscalings(xs) - coeff * ⊗(terms...) -end - -function isnotflat_precheck(*) - function (x) - operation(x) === (*) || return false - args = arguments(x) - for t in args - if isexpr(t) && operation(t) === (*) - return true - end - end - return false - end -end - ## # Simplification rules ## -# Flattening expressions -RULES_FLATTEN = [ - @rule(~x::isnotflat_precheck(⊗) => flatten_term(⊗, ~x)), - @rule ⊗(~~xs::hasscalings) => prefactorscalings_rule(~~xs) -] - # Pauli identities RULES_PAULI = [ @rule(~o1::_isa(XGate)*~o2::_isa(XGate) => I), @@ -110,7 +60,7 @@ RULES_ALL = [RULES_PAULI; RULES_COMMUTATOR; RULES_ANTICOMMUTATOR] ## # Rewriters ## -qsimplify_flatten = Chain(RULES_FLATTEN) + qsimplify_anticommutator = Chain(RULES_ANTICOMMUTATOR) qsimplify_pauli = Chain(RULES_PAULI) qsimplify_commutator = Chain(RULES_COMMUTATOR) diff --git a/src/QSymbolicsBase/utils.jl b/src/QSymbolicsBase/utils.jl new file mode 100644 index 0000000..1ae7e70 --- /dev/null +++ b/src/QSymbolicsBase/utils.jl @@ -0,0 +1,46 @@ +function prefactorscalings(xs) + terms = [] + coeff = 1::Any + for x in xs + if isa(x, SScaledOperator) + coeff *= x.coeff + push!(terms, x.obj) + else + push!(terms,x) + end + end + coeff, terms +end + +function flattenop(f, terms) + newterms = [] + for obj in terms + if isexpr(obj) && operation(obj) === f + append!(newterms, arguments(obj)) + else + push!(newterms, obj) + end + end + newterms +end + +function countmap(samples) # A simpler version of StatsBase.countmap, because StatsBase is slow to import + counts = Dict{Any,Any}() + for s in samples + counts[s] = get(counts, s, 0)+1 + end + counts +end + +function countmap_flatten(samples, flattenhead) + counts = Dict{Any,Any}() + for s in samples + if isexpr(s) && s isa flattenhead # TODO Could you use the TermInterface `operation` here instead of `flattenhead`? + coef, term = arguments(s) + counts[term] = get(counts, term, 0)+coef + else + counts[s] = get(counts, s, 0)+1 + end + end + counts +end \ No newline at end of file From e41dc7e6074b9439bcad2041d8d585483fa24e6d Mon Sep 17 00:00:00 2001 From: apkille Date: Tue, 2 Jul 2024 11:13:02 -0400 Subject: [PATCH 15/22] doctest and piracy fixes --- src/QSymbolicsBase/QSymbolicsBase.jl | 2 +- src/QSymbolicsBase/basic_ops_homogeneous.jl | 6 ------ src/QSymbolicsBase/linalg.jl | 15 ++++++++++----- src/QSymbolicsBase/literal_objects.jl | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index 67395be..80a553d 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -11,7 +11,7 @@ import LinearAlgebra: eigvecs,ishermitian,inv import QuantumInterface: apply!, tensor, ⊗, - basis,Basis,SpinBasis,FockBasis, + basis,Basis,SpinBasis,FockBasis,CompositeBasis, nqubits, projector,dagger,tr,ptrace, AbstractBra,AbstractKet,AbstractOperator,AbstractSuperOperator diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index 787c385..2714e52 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -201,9 +201,6 @@ julia> @op A; @op B; julia> commutator(A, B) [A,B] -julia> expand(commutator(A, B)) -(-1BA+AB) - julia> commutator(A, A) 𝟎 ``` @@ -235,9 +232,6 @@ julia> @op A; @op B; julia> anticommutator(A, B) {A,B} - -julia> expand(anticommutator(A, B)) -(AB+BA) ``` """ @withmetadata struct SAnticommutator <: Symbolic{AbstractOperator} diff --git a/src/QSymbolicsBase/linalg.jl b/src/QSymbolicsBase/linalg.jl index 1771c3a..7d40ff7 100644 --- a/src/QSymbolicsBase/linalg.jl +++ b/src/QSymbolicsBase/linalg.jl @@ -129,10 +129,15 @@ Base.isequal(x::STrace, y::STrace) = isequal(x.op, y.op) """Partial trace over system i of a composite quantum system ```jldoctest -julia> @op A; @op B; +julia> @op 𝒪 SpinBasis(1//2) ⊗ SpinBasis(1//2); + +julia> op = ptrace(𝒪, 1) +tr1(𝒪) -julia> ptrace(A, 1) -tr1(A) +julia> QuantumSymbolics.basis(op) +Spin(1/2) + +julia> @op A; @op B; julia> ptrace(A⊗B, 1) (tr(A))B @@ -175,7 +180,7 @@ function basis(x::SPartialTrace) end Base.show(io::IO, x::SPartialTrace) = print(io, "tr$(x.sys)($(x.obj))") function ptrace(x::Symbolic{AbstractOperator}, s) - if isa(basis(x), QuantumInterface.CompositeBasis) + if isa(basis(x), CompositeBasis) SPartialTrace(x, s) else throw(ArgumentError("cannot take partial trace of a single quantum system")) @@ -184,7 +189,7 @@ end function ptrace(x::STensorOperator, s) terms = arguments(x) newterms = [] - if isa(basis(terms[s]), QuantumInterface.CompositeBasis) + if isa(basis(terms[s]), CompositeBasis) SPartial(x, s) else sys_op = terms[s] diff --git a/src/QSymbolicsBase/literal_objects.jl b/src/QSymbolicsBase/literal_objects.jl index b62c963..a65c850 100644 --- a/src/QSymbolicsBase/literal_objects.jl +++ b/src/QSymbolicsBase/literal_objects.jl @@ -106,4 +106,4 @@ symbollabel(x::SZero) = "𝟎" basis(x::SZero) = nothing Base.show(io::IO, x::SZero) = print(io, symbollabel(x)) -Base.iszero(x::Union{SymQObj, Symbolic{Number}, Symbolic{Complex}}) = isa(x, SZero) +Base.iszero(x::SymQObj) = isa(x, SZero) From 5997dfb382c4fa49715db6979bdf611822524b59 Mon Sep 17 00:00:00 2001 From: apkille Date: Tue, 2 Jul 2024 15:17:29 -0400 Subject: [PATCH 16/22] update ptrace and added tests --- src/QSymbolicsBase/QSymbolicsBase.jl | 5 ++- src/QSymbolicsBase/basic_ops_homogeneous.jl | 2 +- src/QSymbolicsBase/linalg.jl | 40 ++++++++++++------- src/QSymbolicsBase/rules.jl | 5 ++- test/test_expand.jl | 3 ++ test/test_trace.jl | 43 +++++++++++++++++---- 6 files changed, 71 insertions(+), 27 deletions(-) diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index 80a553d..82737fa 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -35,8 +35,9 @@ export SymQObj,QObj, SScaled,SScaledBra,SScaledOperator,SScaledKet, STensorBra,STensorKet,STensorOperator, SZeroBra,SZeroKet,SZeroOperator, - SProjector,MixedState,IdentityOp,SInvOperator, - SApplyKet,SApplyBra,SMulOperator,SSuperOpApply,SCommutator,SAnticommutator,SDagger,SBraKet,SOuterKetBra, + MixedState,IdentityOp, + SProjector,SDagger,STrace,SPartialTrace,SInvOperator, + SApplyKet,SApplyBra,SMulOperator,SSuperOpApply,SCommutator,SAnticommutator,SBraKet,SOuterKetBra, HGate,XGate,YGate,ZGate,CPHASEGate,CNOTGate, XBasisState,YBasisState,ZBasisState, NumberOp,CreateOp,DestroyOp, diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index 2714e52..8656b18 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -147,7 +147,7 @@ function Base.:(*)(xs::Symbolic{AbstractOperator}...) end end Base.show(io::IO, x::SMulOperator) = print(io, join(map(string, arguments(x)),"")) -basis(x::SMulOperator) = basis(x.terms) +basis(x::SMulOperator) = basis(first(x.terms)) """Tensor product of quantum objects (kets, operators, or bras) diff --git a/src/QSymbolicsBase/linalg.jl b/src/QSymbolicsBase/linalg.jl index 7d40ff7..4dd6ffc 100644 --- a/src/QSymbolicsBase/linalg.jl +++ b/src/QSymbolicsBase/linalg.jl @@ -129,7 +129,7 @@ Base.isequal(x::STrace, y::STrace) = isequal(x.op, y.op) """Partial trace over system i of a composite quantum system ```jldoctest -julia> @op 𝒪 SpinBasis(1//2) ⊗ SpinBasis(1//2); +julia> @op 𝒪 SpinBasis(1//2)⊗SpinBasis(1//2); julia> op = ptrace(𝒪, 1) tr1(𝒪) @@ -180,21 +180,15 @@ function basis(x::SPartialTrace) end Base.show(io::IO, x::SPartialTrace) = print(io, "tr$(x.sys)($(x.obj))") function ptrace(x::Symbolic{AbstractOperator}, s) - if isa(basis(x), CompositeBasis) - SPartialTrace(x, s) + ex = isexpr(x) ? qexpand(x) : x + if isa(ex, typeof(x)) + if isa(basis(x), CompositeBasis) + SPartialTrace(x, s) + else + throw(ArgumentError("cannot take partial trace of a single quantum system")) + end else - throw(ArgumentError("cannot take partial trace of a single quantum system")) - end -end -function ptrace(x::STensorOperator, s) - terms = arguments(x) - newterms = [] - if isa(basis(terms[s]), CompositeBasis) - SPartial(x, s) - else - sys_op = terms[s] - new_terms = deleteat!(copy(terms), s) - isone(length(new_terms)) ? tr(sys_op)*first(new_terms) : tr(sys_op)*STensorOperator(new_terms) + ptrace(ex, s) end end function ptrace(x::SAddOperator, s) @@ -212,6 +206,22 @@ function ptrace(x::SAddOperator, s) end (+)(add_terms...) end +function ptrace(x::STensorOperator, s) + ex = qexpand(x) + if isa(ex, SAddOperator) + ptrace(ex, s) + else + terms = arguments(ex) + newterms = [] + if isa(basis(terms[s]), CompositeBasis) + SPartial(ex, s) + else + sys_op = terms[s] + new_terms = deleteat!(copy(terms), s) + isone(length(new_terms)) ? tr(sys_op)*first(new_terms) : tr(sys_op)*STensorOperator(new_terms) + end + end +end """Inverse Operator diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index 38db184..66494ae 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -102,9 +102,10 @@ RULES_EXPAND = [ @rule(+(~~ops) ⊗ ~o1 => +(map(op -> op ⊗ ~o1, ~~ops)...)), @rule(~o1 * +(~~ops) => +(map(op -> ~o1 * op, ~~ops)...)), @rule(+(~~ops) * ~o1 => +(map(op -> op * ~o1, ~~ops)...)), - @rule(+(~~ops) * ~o1 => +(map(op -> op * ~o1, ~~ops)...)), + @rule(⊗(~~ops1::_vecisa(Symbolic{AbstractBra})) * ⊗(~~ops2::_vecisa(Symbolic{AbstractKet})) => *(map(*, ~~ops1, ~~ops2)...)), @rule(⊗(~~ops1::_vecisa(Symbolic{AbstractOperator})) * ⊗(~~ops2::_vecisa(Symbolic{AbstractOperator})) => ⊗(map(*, ~~ops1, ~~ops2)...)), - @rule(⊗(~~ops1::_vecisa(Symbolic{AbstractBra})) * ⊗(~~ops2::_vecisa(Symbolic{AbstractKet})) => *(map(*, ~~ops1, ~~ops2)...)) + @rule(~o1::_isa(Symbolic{AbstractOperator}) * ⊗(~~ops) => ⊗(map(op -> ~o1 * op, ~~ops)...)), + @rule(⊗(~~ops) * ~o1::_isa(Symbolic{AbstractOperator}) => ⊗(map(op -> op * ~o1, ~~ops)...)), ] # diff --git a/test/test_expand.jl b/test/test_expand.jl index fd7fc30..8dab6b3 100644 --- a/test/test_expand.jl +++ b/test/test_expand.jl @@ -26,6 +26,9 @@ using Test @test isequal(qexpand((B+C+D)*A), B*A + C*A + D*A) @test isequal(qexpand(commutator(A, B) * C), A*B*C - B*A*C) + @test isequal(qexpand(A*(B⊗C⊗D)), (A*B)⊗(A*C)⊗(A*D)) + @test isequal(qexpand((B⊗C⊗D)*A), (B*A)⊗(C*A)⊗(D*A)) + @test isequal(qexpand((A⊗B)*(C⊗D)), (A*C)⊗(B*D)) @test isequal(qexpand((b₁⊗b₂)*(k₁⊗k₂)), (b₁*k₁)*(b₂*k₂)) end \ No newline at end of file diff --git a/test/test_trace.jl b/test/test_trace.jl index 1343b89..f4e9f5c 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -3,20 +3,49 @@ using Test @bra b₁; @bra b₂; @ket k₁; @ket k₂; -@op A; @op B; @op C; +@op A; @op B; @op C; @op D; @op E; @op F; @op 𝒪 SpinBasis(1//2)⊗SpinBasis(1//2); @testset "trace tests" begin @test isequal(tr(2*A), 2*tr(A)) @test isequal(tr(A+B), tr(A)+tr(B)) @test isequal(tr(k₁*b₁), b₁*k₁) @test isequal(tr(commutator(A, B)), 0) - @test isequal(tr((⊗)(A, B, C)), tr(A)*tr(B)*tr(C)) + @test isequal(tr(A⊗B⊗C), tr(A)*tr(B)*tr(C)) end +exp1 = A⊗B⊗C +exp2 = (k₁*b₁)⊗A + (k₂*b₂)⊗B +exp3 = A⊗(B⊗C + D⊗E) +exp4 = A⊗(B⊗C + D⊗E)*F @testset "partial trace tests" begin - @test isequal(ptrace((⊗)(A, B, C), 1), tr(A)*(B⊗C)) - @test isequal(ptrace((⊗)(A, B, C), 2), tr(B)*(A⊗C)) - @test isequal(ptrace((⊗)(A, B, C), 3), tr(C)*(A⊗B)) - @test isequal(ptrace((k₁*b₁)⊗A + (k₂*b₂)⊗B, 1), (b₁*k₁)*A + (b₂*k₂)*B) - @test isequal(ptrace((k₁*b₁)⊗A + (k₂*b₂)⊗B, 2), tr(A)*(k₁*b₁) + tr(B)*(k₂*b₂)) + @test isequal(ptrace(𝒪, 1), SPartialTrace(𝒪, 1)) + @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 1)), SpinBasis(1//2)) + @test isequal(ptrace(𝒪, 2), SPartialTrace(𝒪, 2)) + @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 2)), SpinBasis(1//2)) + + @test isequal(ptrace(exp1, 1), tr(A)*(B⊗C)) + @test isequal(QuantumSymbolics.basis(ptrace(exp1, 1)), SpinBasis(1//2)⊗SpinBasis(1//2)) + @test isequal(ptrace(exp1, 2), tr(B)*(A⊗C)) + @test isequal(QuantumSymbolics.basis(ptrace(exp1, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) + @test isequal(ptrace(exp1, 3), tr(C)*(A⊗B)) + @test isequal(QuantumSymbolics.basis(ptrace(exp1, 3)), SpinBasis(1//2)⊗SpinBasis(1//2)) + + @test isequal(ptrace(exp2, 1), (b₁*k₁)*A + (b₂*k₂)*B) + @test isequal(QuantumSymbolics.basis(ptrace(exp2, 1)), SpinBasis(1//2)) + @test isequal(ptrace(exp2, 2), tr(A)*(k₁*b₁) + tr(B)*(k₂*b₂)) + @test isequal(QuantumSymbolics.basis(ptrace(exp2, 2)), SpinBasis(1//2)) + + @test isequal(ptrace(exp3, 1), tr(A)*(B⊗C) + tr(A)*(D⊗E)) + @test isequal(QuantumSymbolics.basis(ptrace(exp3, 1)), SpinBasis(1//2)⊗SpinBasis(1//2)) + @test isequal(ptrace(exp3, 2), tr(B)*(A⊗C) + tr(D)*(A⊗E)) + @test isequal(QuantumSymbolics.basis(ptrace(exp3, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) + @test isequal(ptrace(exp3, 3), tr(C)*(A⊗B) + tr(E)*(A⊗D)) + @test isequal(QuantumSymbolics.basis(ptrace(exp3, 3)), SpinBasis(1//2)⊗SpinBasis(1//2)) + + @test isequal(ptrace(exp4, 1), tr(A*F)*((B*F)⊗(C*F)) + tr(A*F)*((D*F)⊗(E*F))) + @test isequal(QuantumSymbolics.basis(ptrace(exp4, 1)), SpinBasis(1//2)⊗SpinBasis(1//2)) + @test isequal(ptrace(exp4, 2), tr(B*F)*((A*F)⊗(C*F)) + tr(D*F)*((A*F)⊗(E*F))) + @test isequal(QuantumSymbolics.basis(ptrace(exp4, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) + @test isequal(ptrace(exp4, 3), tr(C*F)*((A*F)⊗(B*F)) + tr(E*F)*((A*F)⊗(D*F))) + @test isequal(QuantumSymbolics.basis(ptrace(exp4, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) end From 3e9f5e70ea273a87bce2165214b41837912c364f Mon Sep 17 00:00:00 2001 From: apkille Date: Fri, 5 Jul 2024 17:05:05 -0400 Subject: [PATCH 17/22] fix basis consistency, ptrace, and superops --- src/QSymbolicsBase/QSymbolicsBase.jl | 6 +- src/QSymbolicsBase/basic_ops_homogeneous.jl | 26 +++++-- src/QSymbolicsBase/basic_ops_inhomogeneous.jl | 50 +++++++++----- src/QSymbolicsBase/basic_superops.jl | 17 ++--- src/QSymbolicsBase/linalg.jl | 44 ++++++++---- src/QSymbolicsBase/predefined.jl | 3 +- src/QSymbolicsBase/utils.jl | 2 +- test/test_basis_consistency.jl | 13 ++++ test/test_expand.jl | 3 - test/test_superop.jl | 2 +- test/test_trace.jl | 69 ++++++++++--------- 11 files changed, 149 insertions(+), 86 deletions(-) diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index 82737fa..d0eb12a 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -11,7 +11,7 @@ import LinearAlgebra: eigvecs,ishermitian,inv import QuantumInterface: apply!, tensor, ⊗, - basis,Basis,SpinBasis,FockBasis,CompositeBasis, + basis,Basis,samebases,IncompatibleBases,SpinBasis,FockBasis,CompositeBasis, nqubits, projector,dagger,tr,ptrace, AbstractBra,AbstractKet,AbstractOperator,AbstractSuperOperator @@ -23,12 +23,12 @@ export SymQObj,QObj, apply!, express, tensor,⊗, - dagger,projector,commutator,anticommutator,expand,tr,ptrace, + dagger,projector,commutator,anticommutator,tr,ptrace, I,X,Y,Z,σˣ,σʸ,σᶻ,Pm,Pp,σ₋,σ₊, H,CNOT,CPHASE,XCX,XCY,XCZ,YCX,YCY,YCZ,ZCX,ZCY,ZCZ, X1,X2,Y1,Y2,Z1,Z2,X₁,X₂,Y₁,Y₂,Z₁,Z₂,L0,L1,Lp,Lm,Lpi,Lmi,L₀,L₁,L₊,L₋,L₊ᵢ,L₋ᵢ, vac,F₀,F0,F₁,F1, - N,n̂,Create,âꜛ,Destroy,â,SpinBasis,FockBasis, + N,n̂,Create,âꜛ,Destroy,â,basis,SpinBasis,FockBasis, SBra,SKet,SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator,SSuperOperator, @ket,@bra,@op,@superop, SAdd,SAddBra,SAddKet,SAddOperator, diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index 8656b18..97322a5 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -139,9 +139,13 @@ children(x::SMulOperator) = [:*;x.terms] function Base.:(*)(xs::Symbolic{AbstractOperator}...) zero_ind = findfirst(x->iszero(x), xs) if isnothing(zero_ind) - terms = flattenop(*, collect(xs)) - coeff, cleanterms = prefactorscalings(terms) - coeff * SMulOperator(cleanterms) + if any(x->!(samebases(basis(x),basis(first(xs)))),xs) + throw(IncompatibleBases()) + else + terms = flattenop(*, collect(xs)) + coeff, cleanterms = prefactorscalings(terms) + coeff * SMulOperator(cleanterms) + end else SZeroOperator() end @@ -216,8 +220,12 @@ operation(x::SCommutator) = commutator head(x::SCommutator) = :commutator children(x::SCommutator) = [:commutator, x.op1, x.op2] function commutator(o1::Symbolic{AbstractOperator}, o2::Symbolic{AbstractOperator}) - coeff, cleanterms = prefactorscalings([o1 o2]) - cleanterms[1] === cleanterms[2] ? SZeroOperator() : coeff * SCommutator(cleanterms...) + if !(samebases(basis(o1),basis(o2))) + throw(IncompatibleBases()) + else + coeff, cleanterms = prefactorscalings([o1 o2]) + cleanterms[1] === cleanterms[2] ? SZeroOperator() : coeff * SCommutator(cleanterms...) + end end commutator(o1::SZeroOperator, o2::Symbolic{AbstractOperator}) = SZeroOperator() commutator(o1::Symbolic{AbstractOperator}, o2::SZeroOperator) = SZeroOperator() @@ -245,8 +253,12 @@ operation(x::SAnticommutator) = anticommutator head(x::SAnticommutator) = :anticommutator children(x::SAnticommutator) = [:anticommutator, x.op1, x.op2] function anticommutator(o1::Symbolic{AbstractOperator}, o2::Symbolic{AbstractOperator}) - coeff, cleanterms = prefactorscalings([o1 o2]) - coeff * SAnticommutator(cleanterms...) + if !(samebases(basis(o1),basis(o2))) + throw(IncompatibleBases()) + else + coeff, cleanterms = prefactorscalings([o1 o2]) + coeff * SAnticommutator(cleanterms...) + end end anticommutator(o1::SZeroOperator, o2::Symbolic{AbstractOperator}) = SZeroOperator() anticommutator(o1::Symbolic{AbstractOperator}, o2::SZeroOperator) = SZeroOperator() diff --git a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl index 86d41f7..616639d 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -14,10 +14,6 @@ A|k⟩ @withmetadata struct SApplyKet <: Symbolic{AbstractKet} op ket - function SApplyKet(o, k) - coeff, cleanterms = prefactorscalings([o k]) - coeff*new(cleanterms...) - end end isexpr(::SApplyKet) = true iscall(::SApplyKet) = true @@ -25,7 +21,14 @@ arguments(x::SApplyKet) = [x.op,x.ket] operation(x::SApplyKet) = * head(x::SApplyKet) = :* children(x::SApplyKet) = [:*,x.op,x.ket] -Base.:(*)(op::Symbolic{AbstractOperator}, k::Symbolic{AbstractKet}) = SApplyKet(op,k) +function Base.:(*)(op::Symbolic{AbstractOperator}, k::Symbolic{AbstractKet}) + if !(samebases(basis(op),basis(k))) + throw(IncompatibleBases()) + else + coeff, cleanterms = prefactorscalings([op k]) + coeff*SApplyKet(cleanterms...) + end +end Base.:(*)(op::SZeroOperator, k::Symbolic{AbstractKet}) = SZeroKet() Base.:(*)(op::Symbolic{AbstractOperator}, k::SZeroKet) = SZeroKet() Base.:(*)(op::SZeroOperator, k::SZeroKet) = SZeroKet() @@ -44,10 +47,6 @@ julia> b*A @withmetadata struct SApplyBra <: Symbolic{AbstractBra} bra op - function SApplyBra(b, o) - coeff, cleanterms = prefactorscalings([b o]) - coeff*new(cleanterms...) - end end isexpr(::SApplyBra) = true iscall(::SApplyBra) = true @@ -55,7 +54,14 @@ arguments(x::SApplyBra) = [x.bra,x.op] operation(x::SApplyBra) = * head(x::SApplyBra) = :* children(x::SApplyBra) = [:*,x.bra,x.op] -Base.:(*)(b::Symbolic{AbstractBra}, op::Symbolic{AbstractOperator}) = SApplyBra(b,op) +function Base.:(*)(b::Symbolic{AbstractBra}, op::Symbolic{AbstractOperator}) + if !(samebases(basis(b),basis(op))) + throw(IncompatibleBases()) + else + coeff, cleanterms = prefactorscalings([b op]) + coeff*SApplyBra(cleanterms...) + end +end Base.:(*)(b::SZeroBra, op::Symbolic{AbstractOperator}) = SZeroBra() Base.:(*)(b::Symbolic{AbstractBra}, op::SZeroOperator) = SZeroBra() Base.:(*)(b::SZeroBra, op::SZeroOperator) = SZeroBra() @@ -81,13 +87,20 @@ arguments(x::SBraKet) = [x.bra,x.ket] operation(x::SBraKet) = * head(x::SBraKet) = :* children(x::SBraKet) = [:*,x.bra,x.ket] -Base.:(*)(b::Symbolic{AbstractBra}, k::Symbolic{AbstractKet}) = SBraKet(b,k) +function Base.:(*)(b::Symbolic{AbstractBra}, k::Symbolic{AbstractKet}) + if !(samebases(basis(b),basis(k))) + throw(IncompatibleBases()) + else + coeff, cleanterms = prefactorscalings([b k]) + coeff == 1 ? SBraKet(cleanterms...) : coeff*SBraKet(cleanterms...) + end +end Base.:(*)(b::SZeroBra, k::Symbolic{AbstractKet}) = 0 Base.:(*)(b::Symbolic{AbstractBra}, k::SZeroKet) = 0 Base.:(*)(b::SZeroBra, k::SZeroKet) = 0 Base.show(io::IO, x::SBraKet) = begin print(io,x.bra); print(io,x.ket) end Base.hash(x::SBraKet, h::UInt) = hash((head(x), arguments(x)), h) -maketerm(::Type{<:SBraKet}, f, a, t, m) = f(a...) +maketerm(::Type{SBraKet}, f, a, t, m) = f(a...) Base.isequal(x::SBraKet, y::SBraKet) = isequal(x.bra, y.bra) && isequal(x.ket, y.ket) """Symbolic outer product of a ket and a bra @@ -101,10 +114,6 @@ julia> k*b @withmetadata struct SOuterKetBra <: Symbolic{AbstractOperator} ket bra - function SOuterKetBra(k, b) - coeff, cleanterms = prefactorscalings([k b]) - coeff*new(cleanterms...) - end end isexpr(::SOuterKetBra) = true iscall(::SOuterKetBra) = true @@ -112,7 +121,14 @@ arguments(x::SOuterKetBra) = [x.ket,x.bra] operation(x::SOuterKetBra) = * head(x::SOuterKetBra) = :* children(x::SOuterKetBra) = [:*,x.ket,x.bra] -Base.:(*)(k::Symbolic{AbstractKet}, b::Symbolic{AbstractBra}) = SOuterKetBra(k,b) +function Base.:(*)(k::Symbolic{AbstractKet}, b::Symbolic{AbstractBra}) + if !(samebases(basis(k),basis(b))) + throw(IncompatibleBases()) + else + coeff, cleanterms = prefactorscalings([k b]) + coeff*SOuterKetBra(cleanterms...) + end +end Base.:(*)(k::SZeroKet, b::Symbolic{AbstractBra}) = SZeroOperator() Base.:(*)(k::Symbolic{AbstractKet}, b::SZeroBra) = SZeroOperator() Base.:(*)(k::SZeroKet, b::SZeroBra) = SZeroOperator() diff --git a/src/QSymbolicsBase/basic_superops.jl b/src/QSymbolicsBase/basic_superops.jl index 36746b6..d164992 100644 --- a/src/QSymbolicsBase/basic_superops.jl +++ b/src/QSymbolicsBase/basic_superops.jl @@ -5,11 +5,10 @@ """Kraus representation of a quantum channel ```jldoctest -julia> @superop ℰ; - julia> @op A₁; @op A₂; @op A₃; -julia> K = kraus(ℰ, A₁, A₂, A₃); +julia> K = kraus(A₁, A₂, A₃) +𝒦(A₁,A₂,A₃) julia> @op ρ; @@ -18,19 +17,17 @@ julia> K*ρ ``` """ @withmetadata struct KrausRepr <: Symbolic{AbstractSuperOperator} - sop krausops end isexpr(::KrausRepr) = true iscall(::KrausRepr) = true -arguments(x::KrausRepr) = [x.sop, x.krausops] +arguments(x::KrausRepr) = x.krausops operation(x::KrausRepr) = kraus head(x::KrausRepr) = :kraus -children(x::KrausRepr) = [:kraus, x.sop, x.krausops] -kraus(s::Symbolic{AbstractSuperOperator}, xs::Symbolic{AbstractOperator}...) = KrausRepr(s,collect(xs)) -symbollabel(x::KrausRepr) = symbollabel(x.sop) -basis(x::KrausRepr) = basis(x.sop) -Base.show(io::IO, x::KrausRepr) = print(io, symbollabel(x)) +children(x::KrausRepr) = [:kraus; x.krausops] +kraus(xs::Symbolic{AbstractOperator}...) = KrausRepr(collect(xs)) +basis(x::KrausRepr) = basis(first(x.krausops)) +Base.show(io::IO, x::KrausRepr) = print(io, "𝒦("*join([symbollabel(i) for i in x.krausops], ",")*")") ## # Superoperator operations diff --git a/src/QSymbolicsBase/linalg.jl b/src/QSymbolicsBase/linalg.jl index 4dd6ffc..4e9c6ae 100644 --- a/src/QSymbolicsBase/linalg.jl +++ b/src/QSymbolicsBase/linalg.jl @@ -184,6 +184,8 @@ function ptrace(x::Symbolic{AbstractOperator}, s) if isa(ex, typeof(x)) if isa(basis(x), CompositeBasis) SPartialTrace(x, s) + elseif s==1 + tr(x) else throw(ArgumentError("cannot take partial trace of a single quantum system")) end @@ -192,19 +194,37 @@ function ptrace(x::Symbolic{AbstractOperator}, s) end end function ptrace(x::SAddOperator, s) - terms = arguments(x) add_terms = [] - for i in terms - if isexpr(i) && operation(i) === ⊗ - isa(i, SScaledOperator) ? prod_terms = arguments(i.obj) : prod_terms = arguments(i) - sys_op = prod_terms[s] - new_terms = deleteat!(copy(prod_terms), s) - isone(length(new_terms)) ? push!(add_terms, tr(sys_op)*first(new_terms)) : push!(add_terms, tr(sys_op)*STensorOperator(new_terms)) - else - throw(ArgumentError("cannot take partial trace of a single quantum system")) + if isa(basis(x), CompositeBasis) + for i in arguments(x) + if isexpr(i) + if isa(i, SScaledOperator) && operation(i.obj) === ⊗ + prod_terms = arguments(i.obj) + coeff = i.coeff + elseif operation(i) === ⊗ + prod_terms = arguments(i) + coeff = 1 + else + return SPartialTrace(x,s) + end + else + return SPartialTrace(x,s) + end + if any(j -> isa(basis(j), CompositeBasis), prod_terms) + return SPartialTrace(x,s) + else + sys_op = coeff*prod_terms[s] + new_terms = deleteat!(copy(prod_terms), s) + trace = tr(sys_op) + isone(length(new_terms)) ? push!(add_terms, trace*first(new_terms)) : push!(add_terms, trace*(⊗)(new_terms...)) + end end + (+)(add_terms...) + elseif s==1 + tr(x) + else + throw(ArgumentError("cannot take partial trace of a single quantum system")) end - (+)(add_terms...) end function ptrace(x::STensorOperator, s) ex = qexpand(x) @@ -213,8 +233,8 @@ function ptrace(x::STensorOperator, s) else terms = arguments(ex) newterms = [] - if isa(basis(terms[s]), CompositeBasis) - SPartial(ex, s) + if any(i -> isa(basis(i), CompositeBasis), terms) + SPartialTrace(ex, s) else sys_op = terms[s] new_terms = deleteat!(copy(terms), s) diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 7e599cc..bc15880 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -179,7 +179,7 @@ const CPHASE = CPHASEGate() abstract type AbstractSingleBosonOp <: Symbolic{AbstractOperator} end abstract type AbstractSingleBosonGate <: AbstractSingleBosonOp end # TODO maybe an IsUnitaryTrait is a better choice -isexpr(::AbstractSingleBosonGate) = false +isexpr(::AbstractSingleBosonOp) = false basis(::AbstractSingleBosonGate) = inf_fock_basis @withmetadata struct NumberOp <: AbstractSingleBosonOp end @@ -188,6 +188,7 @@ symbollabel(::NumberOp) = "n" symbollabel(::CreateOp) = "a†" @withmetadata struct DestroyOp <: AbstractSingleBosonOp end symbollabel(::DestroyOp) = "a" +basis(::Union{NumberOp, CreateOp, DestroyOp}) = inf_fock_basis """Number operator, also available as the constant `n̂`""" const N = const n̂ = NumberOp() diff --git a/src/QSymbolicsBase/utils.jl b/src/QSymbolicsBase/utils.jl index b14f582..5afe9f1 100644 --- a/src/QSymbolicsBase/utils.jl +++ b/src/QSymbolicsBase/utils.jl @@ -2,7 +2,7 @@ function prefactorscalings(xs) terms = [] coeff = 1::Any for x in xs - if isa(x, SScaledOperator) + if isa(x, SScaled) coeff *= x.coeff push!(terms, x.obj) elseif isa(x, Union{Number, Symbolic{Number}}) diff --git a/test/test_basis_consistency.jl b/test/test_basis_consistency.jl index df4da34..44ecd44 100644 --- a/test/test_basis_consistency.jl +++ b/test/test_basis_consistency.jl @@ -1,5 +1,7 @@ using Test using QuantumSymbolics +using QuantumOptics +using QuantumInterface: IncompatibleBases @test express(Z*Z1) == express(Z1) @test express(Z*Z2) == -express(Z2) @@ -11,3 +13,14 @@ using QuantumSymbolics @test express(Pp*Z2) == express(Z1) @test express(Pm*L0) == express(L1) @test express(Pp*L1) == express(L0) + +@op A SpinBasis(1//2) ⊗ SpinBasis(1//2); @op B; +@ket k; @bra b; @ket l SpinBasis(1//2) ⊗ SpinBasis(1//2); + +@test_throws IncompatibleBases A*B +@test_throws IncompatibleBases commutator(A, B) +@test_throws IncompatibleBases anticommutator(A, B) +@test_throws IncompatibleBases A*k +@test_throws IncompatibleBases b*A +@test_throws IncompatibleBases l*b +@test_throws IncompatibleBases b*l \ No newline at end of file diff --git a/test/test_expand.jl b/test/test_expand.jl index 8dab6b3..fd7fc30 100644 --- a/test/test_expand.jl +++ b/test/test_expand.jl @@ -26,9 +26,6 @@ using Test @test isequal(qexpand((B+C+D)*A), B*A + C*A + D*A) @test isequal(qexpand(commutator(A, B) * C), A*B*C - B*A*C) - @test isequal(qexpand(A*(B⊗C⊗D)), (A*B)⊗(A*C)⊗(A*D)) - @test isequal(qexpand((B⊗C⊗D)*A), (B*A)⊗(C*A)⊗(D*A)) - @test isequal(qexpand((A⊗B)*(C⊗D)), (A*C)⊗(B*D)) @test isequal(qexpand((b₁⊗b₂)*(k₁⊗k₂)), (b₁*k₁)*(b₂*k₂)) end \ No newline at end of file diff --git a/test/test_superop.jl b/test/test_superop.jl index 73a9831..d000c14 100644 --- a/test/test_superop.jl +++ b/test/test_superop.jl @@ -45,7 +45,7 @@ noisy_pair_fromdm = (noiseop ⊗ noiseop) * pure_pair_dm @test express(noisy_pair) ≈ express(noisy_pair_fromdm) @op A; @op B; @op C; @op O; @ket k; -@superop S; K = kraus(S, A, B, C); +@superop S; K = kraus(A, B, C); diff --git a/test/test_trace.jl b/test/test_trace.jl index f4e9f5c..bc03e21 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -3,7 +3,9 @@ using Test @bra b₁; @bra b₂; @ket k₁; @ket k₂; -@op A; @op B; @op C; @op D; @op E; @op F; @op 𝒪 SpinBasis(1//2)⊗SpinBasis(1//2); +@op A; @op B; @op C; @op D; @op E; @op F; +@op 𝒪 SpinBasis(1//2)⊗SpinBasis(1//2); @op 𝒫 SpinBasis(1//2)⊗SpinBasis(1//2); +@op ℒ SpinBasis(1//2)⊗SpinBasis(1//2); @testset "trace tests" begin @test isequal(tr(2*A), 2*tr(A)) @@ -13,39 +15,44 @@ using Test @test isequal(tr(A⊗B⊗C), tr(A)*tr(B)*tr(C)) end -exp1 = A⊗B⊗C -exp2 = (k₁*b₁)⊗A + (k₂*b₂)⊗B -exp3 = A⊗(B⊗C + D⊗E) -exp4 = A⊗(B⊗C + D⊗E)*F +exp1 = (k₁*b₁)⊗A + (k₂*b₂)⊗B +exp2 = A⊗(B⊗C + D⊗E) @testset "partial trace tests" begin + + # tests for ptrace(x::Symbolic{AbstractOperator}, s) @test isequal(ptrace(𝒪, 1), SPartialTrace(𝒪, 1)) - @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 1)), SpinBasis(1//2)) @test isequal(ptrace(𝒪, 2), SPartialTrace(𝒪, 2)) + @test isequal(ptrace(A, 1), tr(A)) + @test_throws ArgumentError ptrace(A, 2) + @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 1)), SpinBasis(1//2)) @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 2)), SpinBasis(1//2)) - @test isequal(ptrace(exp1, 1), tr(A)*(B⊗C)) - @test isequal(QuantumSymbolics.basis(ptrace(exp1, 1)), SpinBasis(1//2)⊗SpinBasis(1//2)) - @test isequal(ptrace(exp1, 2), tr(B)*(A⊗C)) - @test isequal(QuantumSymbolics.basis(ptrace(exp1, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) - @test isequal(ptrace(exp1, 3), tr(C)*(A⊗B)) - @test isequal(QuantumSymbolics.basis(ptrace(exp1, 3)), SpinBasis(1//2)⊗SpinBasis(1//2)) - - @test isequal(ptrace(exp2, 1), (b₁*k₁)*A + (b₂*k₂)*B) - @test isequal(QuantumSymbolics.basis(ptrace(exp2, 1)), SpinBasis(1//2)) - @test isequal(ptrace(exp2, 2), tr(A)*(k₁*b₁) + tr(B)*(k₂*b₂)) - @test isequal(QuantumSymbolics.basis(ptrace(exp2, 2)), SpinBasis(1//2)) - - @test isequal(ptrace(exp3, 1), tr(A)*(B⊗C) + tr(A)*(D⊗E)) - @test isequal(QuantumSymbolics.basis(ptrace(exp3, 1)), SpinBasis(1//2)⊗SpinBasis(1//2)) - @test isequal(ptrace(exp3, 2), tr(B)*(A⊗C) + tr(D)*(A⊗E)) - @test isequal(QuantumSymbolics.basis(ptrace(exp3, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) - @test isequal(ptrace(exp3, 3), tr(C)*(A⊗B) + tr(E)*(A⊗D)) - @test isequal(QuantumSymbolics.basis(ptrace(exp3, 3)), SpinBasis(1//2)⊗SpinBasis(1//2)) - - @test isequal(ptrace(exp4, 1), tr(A*F)*((B*F)⊗(C*F)) + tr(A*F)*((D*F)⊗(E*F))) - @test isequal(QuantumSymbolics.basis(ptrace(exp4, 1)), SpinBasis(1//2)⊗SpinBasis(1//2)) - @test isequal(ptrace(exp4, 2), tr(B*F)*((A*F)⊗(C*F)) + tr(D*F)*((A*F)⊗(E*F))) - @test isequal(QuantumSymbolics.basis(ptrace(exp4, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) - @test isequal(ptrace(exp4, 3), tr(C*F)*((A*F)⊗(B*F)) + tr(E*F)*((A*F)⊗(D*F))) - @test isequal(QuantumSymbolics.basis(ptrace(exp4, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) + # tests for ptrace(x::SAddOperator, s) + @test isequal(ptrace(A+B, 1), tr(A+B)) + @test_throws ArgumentError ptrace(A+B, 2) + @test isequal(ptrace(2*(A⊗B)+(C⊗D), 1), 2*tr(A)*B + tr(C)*D) + @test isequal(ptrace((A⊗B)+(C⊗D), 1), tr(A)*B + tr(C)*D) + @test isequal(ptrace((A⊗B⊗C)+(D⊗E⊗F), 1), tr(A)*(B⊗C) + tr(D)*(E⊗F)) + @test isequal(ptrace(𝒪 + 𝒫, 1), SPartialTrace(𝒪 + 𝒫, 1)) + @test isequal(ptrace(𝒪*ℒ + 𝒫*ℒ, 1), SPartialTrace(𝒪*ℒ + 𝒫*ℒ, 1)) + @test isequal(ptrace(𝒪⊗ℒ + 𝒫⊗ℒ, 1), SPartialTrace(𝒪⊗ℒ + 𝒫⊗ℒ, 1)) + + # tests for ptrace(x::STensorOperator, s) + @test isequal(ptrace(A⊗(B⊗C + D⊗E), 1), tr(A)*(B⊗C) + tr(A)*(D⊗E)) + @test isequal(ptrace(𝒪⊗A, 1), SPartialTrace(𝒪⊗A, 1)) + @test isequal(ptrace(A⊗B, 1), tr(A)*B) + @test isequal(ptrace(A⊗B⊗C, 1), tr(A)*(B⊗C)) + + # additional tests + @test isequal(ptrace(exp1, 1), (b₁*k₁)*A + (b₂*k₂)*B) + @test isequal(basis(ptrace(exp1, 1)), SpinBasis(1//2)) + @test isequal(ptrace(exp1, 2), tr(A)*(k₁*b₁) + tr(B)*(k₂*b₂)) + @test isequal(basis(ptrace(exp1, 2)), SpinBasis(1//2)) + + @test isequal(ptrace(exp2, 1), tr(A)*(B⊗C) + tr(A)*(D⊗E)) + @test isequal(basis(ptrace(exp2, 1)), SpinBasis(1//2)⊗SpinBasis(1//2)) + @test isequal(ptrace(exp2, 2), tr(B)*(A⊗C) + tr(D)*(A⊗E)) + @test isequal(basis(ptrace(exp2, 2)), SpinBasis(1//2)⊗SpinBasis(1//2)) + @test isequal(ptrace(exp2, 3), tr(C)*(A⊗B) + tr(E)*(A⊗D)) + @test isequal(basis(ptrace(exp2, 3)), SpinBasis(1//2)⊗SpinBasis(1//2)) end From e9c4c91d4262d85a254294f7149a01105f6b2ce7 Mon Sep 17 00:00:00 2001 From: apkille Date: Sat, 6 Jul 2024 16:28:03 -0400 Subject: [PATCH 18/22] more codecov --- src/QSymbolicsBase/predefined.jl | 2 +- src/QSymbolicsBase/rules.jl | 2 -- test/test_trace.jl | 1 + 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index bc15880..96af471 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -179,7 +179,7 @@ const CPHASE = CPHASEGate() abstract type AbstractSingleBosonOp <: Symbolic{AbstractOperator} end abstract type AbstractSingleBosonGate <: AbstractSingleBosonOp end # TODO maybe an IsUnitaryTrait is a better choice -isexpr(::AbstractSingleBosonOp) = false +isexpr(::AbstractSingleBosonGate) = false basis(::AbstractSingleBosonGate) = inf_fock_basis @withmetadata struct NumberOp <: AbstractSingleBosonOp end diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index 66494ae..d5e140c 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -104,8 +104,6 @@ RULES_EXPAND = [ @rule(+(~~ops) * ~o1 => +(map(op -> op * ~o1, ~~ops)...)), @rule(⊗(~~ops1::_vecisa(Symbolic{AbstractBra})) * ⊗(~~ops2::_vecisa(Symbolic{AbstractKet})) => *(map(*, ~~ops1, ~~ops2)...)), @rule(⊗(~~ops1::_vecisa(Symbolic{AbstractOperator})) * ⊗(~~ops2::_vecisa(Symbolic{AbstractOperator})) => ⊗(map(*, ~~ops1, ~~ops2)...)), - @rule(~o1::_isa(Symbolic{AbstractOperator}) * ⊗(~~ops) => ⊗(map(op -> ~o1 * op, ~~ops)...)), - @rule(⊗(~~ops) * ~o1::_isa(Symbolic{AbstractOperator}) => ⊗(map(op -> op * ~o1, ~~ops)...)), ] # diff --git a/test/test_trace.jl b/test/test_trace.jl index bc03e21..ef4f59a 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -23,6 +23,7 @@ exp2 = A⊗(B⊗C + D⊗E) @test isequal(ptrace(𝒪, 1), SPartialTrace(𝒪, 1)) @test isequal(ptrace(𝒪, 2), SPartialTrace(𝒪, 2)) @test isequal(ptrace(A, 1), tr(A)) + @test isequal(ptrace(A*(B+C), 1), tr(A*B)+tr(A*C)) @test_throws ArgumentError ptrace(A, 2) @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 1)), SpinBasis(1//2)) @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 2)), SpinBasis(1//2)) From e3d7eb058c66235c259a02131516cfd99e87e30d Mon Sep 17 00:00:00 2001 From: apkille Date: Sat, 6 Jul 2024 16:28:59 -0400 Subject: [PATCH 19/22] typo fix --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index 2ce5bd4..70dcbd2 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -202,4 +202,4 @@ express(MixedState(X1)/2+SProjector(Z1)/2, CliffordRepr()) !!! warning "Stabilizer state expressions" - The state written as $\frac{|Z₁⟩⊗|Z₁⟩+|Z₂⟩⊗|Z₂⟩}{√2}$ is a well known stabilizer state, namely a Bell state. However, automatically expressing it as a stabilizer is a prohibitively expensive computational operation in general. We do not perform that computation automatically. If you want to ensure that states you define can be automatically converted to tableaux for Clifford simulations, avoid using sumation of kets. On the other hand, in all of our Clifford Monte-Carlo simulations, `⊗` is fully supported, as well as [`SProjector`](@ref), [`MixedState`](@ref), [`StabilizerState`](@ref), and sumation of density matrices. + The state written as $\frac{|Z₁⟩⊗|Z₁⟩+|Z₂⟩⊗|Z₂⟩}{√2}$ is a well known stabilizer state, namely a Bell state. However, automatically expressing it as a stabilizer is a prohibitively expensive computational operation in general. We do not perform that computation automatically. If you want to ensure that states you define can be automatically converted to tableaux for Clifford simulations, avoid using summation of kets. On the other hand, in all of our Clifford Monte-Carlo simulations, `⊗` is fully supported, as well as [`SProjector`](@ref), [`MixedState`](@ref), [`StabilizerState`](@ref), and summation of density matrices. From ffd9c2616a6ccd2d31f13cabf3214115868283f2 Mon Sep 17 00:00:00 2001 From: apkille Date: Mon, 8 Jul 2024 17:05:17 -0400 Subject: [PATCH 20/22] boson op change --- src/QSymbolicsBase/predefined.jl | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 96af471..c92c5af 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -180,22 +180,28 @@ const CPHASE = CPHASEGate() abstract type AbstractSingleBosonOp <: Symbolic{AbstractOperator} end abstract type AbstractSingleBosonGate <: AbstractSingleBosonOp end # TODO maybe an IsUnitaryTrait is a better choice isexpr(::AbstractSingleBosonGate) = false +basis(x::AbstractSingleBosonOp) = x.basis basis(::AbstractSingleBosonGate) = inf_fock_basis -@withmetadata struct NumberOp <: AbstractSingleBosonOp end +@withmetadata struct NumberOp <: AbstractSingleBosonOp + basis::Basis +end symbollabel(::NumberOp) = "n" -@withmetadata struct CreateOp <: AbstractSingleBosonOp end +@withmetadata struct CreateOp <: AbstractSingleBosonOp + basis::Basis +end symbollabel(::CreateOp) = "a†" -@withmetadata struct DestroyOp <: AbstractSingleBosonOp end +@withmetadata struct DestroyOp <: AbstractSingleBosonOp + basis::Basis +end symbollabel(::DestroyOp) = "a" -basis(::Union{NumberOp, CreateOp, DestroyOp}) = inf_fock_basis """Number operator, also available as the constant `n̂`""" -const N = const n̂ = NumberOp() +const N = const n̂ = NumberOp(inf_fock_basis) """Creation operator, also available as the constant `âꜛ` - there is no unicode dagger superscript, so we use the uparrow""" -const Create = const âꜛ = CreateOp() +const Create = const âꜛ = CreateOp(inf_fock_basis) """Annihilation operator, also available as the constant `â`""" -const Destroy = const â = DestroyOp() +const Destroy = const â = DestroyOp(inf_fock_basis) ## # Other special or useful objects From bcddbc5cd4ca4f886d495cab6040c242ecd5fe41 Mon Sep 17 00:00:00 2001 From: apkille Date: Fri, 19 Jul 2024 13:55:47 -0400 Subject: [PATCH 21/22] review changes --- src/QSymbolicsBase/basic_superops.jl | 1 + src/QSymbolicsBase/linalg.jl | 22 +++++++++++----------- src/QSymbolicsBase/predefined.jl | 21 +++++++-------------- test/runtests.jl | 1 + test/test_basis_consistency.jl | 13 +++---------- test/test_superop.jl | 4 ++-- test/test_throws.jl | 17 +++++++++++++++++ test/test_trace.jl | 2 -- 8 files changed, 42 insertions(+), 39 deletions(-) create mode 100644 test/test_throws.jl diff --git a/src/QSymbolicsBase/basic_superops.jl b/src/QSymbolicsBase/basic_superops.jl index d164992..d6f0363 100644 --- a/src/QSymbolicsBase/basic_superops.jl +++ b/src/QSymbolicsBase/basic_superops.jl @@ -40,6 +40,7 @@ julia> @op A; @superop S; julia> S*A S[A] +``` """ @withmetadata struct SSuperOpApply <: Symbolic{AbstractOperator} sop diff --git a/src/QSymbolicsBase/linalg.jl b/src/QSymbolicsBase/linalg.jl index 4e9c6ae..c4cd36e 100644 --- a/src/QSymbolicsBase/linalg.jl +++ b/src/QSymbolicsBase/linalg.jl @@ -144,13 +144,13 @@ julia> ptrace(A⊗B, 1) julia> @ket k; @bra b; -julia> pure_state = A ⊗ (k*b) +julia> factorizable = A ⊗ (k*b) (A⊗|k⟩⟨b|) -julia> ptrace(pure_state, 1) +julia> ptrace(factorizable, 1) (tr(A))|k⟩⟨b| -julia> ptrace(pure_state, 2) +julia> ptrace(factorizable, 2) (⟨b||k⟩)A julia> mixed_state = (A⊗(k*b)) + ((k*b)⊗B) @@ -198,21 +198,21 @@ function ptrace(x::SAddOperator, s) if isa(basis(x), CompositeBasis) for i in arguments(x) if isexpr(i) - if isa(i, SScaledOperator) && operation(i.obj) === ⊗ + if isa(i, SScaledOperator) && operation(i.obj) === ⊗ # scaled tensor product prod_terms = arguments(i.obj) coeff = i.coeff - elseif operation(i) === ⊗ + elseif operation(i) === ⊗ # tensor product prod_terms = arguments(i) coeff = 1 - else + else # multiplication of operators with composite basis return SPartialTrace(x,s) end - else - return SPartialTrace(x,s) + else # operator with composite basis + return SPartialTrace(x,s) end - if any(j -> isa(basis(j), CompositeBasis), prod_terms) + if any(j -> isa(basis(j), CompositeBasis), prod_terms) # tensor product of operators with composite bases return SPartialTrace(x,s) - else + else # tensor product without composite basis sys_op = coeff*prod_terms[s] new_terms = deleteat!(copy(prod_terms), s) trace = tr(sys_op) @@ -220,7 +220,7 @@ function ptrace(x::SAddOperator, s) end end (+)(add_terms...) - elseif s==1 + elseif s==1 # partial trace must be over the first system if sum does not have a composite basis tr(x) else throw(ArgumentError("cannot take partial trace of a single quantum system")) diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index c92c5af..2eb194d 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -180,28 +180,21 @@ const CPHASE = CPHASEGate() abstract type AbstractSingleBosonOp <: Symbolic{AbstractOperator} end abstract type AbstractSingleBosonGate <: AbstractSingleBosonOp end # TODO maybe an IsUnitaryTrait is a better choice isexpr(::AbstractSingleBosonGate) = false -basis(x::AbstractSingleBosonOp) = x.basis -basis(::AbstractSingleBosonGate) = inf_fock_basis +basis(x::AbstractSingleBosonOp) = inf_fock_basis -@withmetadata struct NumberOp <: AbstractSingleBosonOp - basis::Basis -end +@withmetadata struct NumberOp <: AbstractSingleBosonOp end symbollabel(::NumberOp) = "n" -@withmetadata struct CreateOp <: AbstractSingleBosonOp - basis::Basis -end +@withmetadata struct CreateOp <: AbstractSingleBosonOp end symbollabel(::CreateOp) = "a†" -@withmetadata struct DestroyOp <: AbstractSingleBosonOp - basis::Basis -end +@withmetadata struct DestroyOp <: AbstractSingleBosonOp end symbollabel(::DestroyOp) = "a" """Number operator, also available as the constant `n̂`""" -const N = const n̂ = NumberOp(inf_fock_basis) +const N = const n̂ = NumberOp() """Creation operator, also available as the constant `âꜛ` - there is no unicode dagger superscript, so we use the uparrow""" -const Create = const âꜛ = CreateOp(inf_fock_basis) +const Create = const âꜛ = CreateOp() """Annihilation operator, also available as the constant `â`""" -const Destroy = const â = DestroyOp(inf_fock_basis) +const Destroy = const â = DestroyOp() ## # Other special or useful objects diff --git a/test/runtests.jl b/test/runtests.jl index 1d57970..ac8fb8b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -37,6 +37,7 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA @doset "zero_obj" @doset "trace" @doset "expand" +@doset "throws" VERSION >= v"1.9" && @doset "doctests" get(ENV,"JET_TEST","")=="true" && @doset "jet" diff --git a/test/test_basis_consistency.jl b/test/test_basis_consistency.jl index 44ecd44..9862da1 100644 --- a/test/test_basis_consistency.jl +++ b/test/test_basis_consistency.jl @@ -1,7 +1,6 @@ using Test using QuantumSymbolics using QuantumOptics -using QuantumInterface: IncompatibleBases @test express(Z*Z1) == express(Z1) @test express(Z*Z2) == -express(Z2) @@ -14,13 +13,7 @@ using QuantumInterface: IncompatibleBases @test express(Pm*L0) == express(L1) @test express(Pp*L1) == express(L0) -@op A SpinBasis(1//2) ⊗ SpinBasis(1//2); @op B; -@ket k; @bra b; @ket l SpinBasis(1//2) ⊗ SpinBasis(1//2); +@op A; @op B; @op C; @op O; @ket k; +@superop S; K = kraus(A, B, C); -@test_throws IncompatibleBases A*B -@test_throws IncompatibleBases commutator(A, B) -@test_throws IncompatibleBases anticommutator(A, B) -@test_throws IncompatibleBases A*k -@test_throws IncompatibleBases b*A -@test_throws IncompatibleBases l*b -@test_throws IncompatibleBases b*l \ No newline at end of file +@test basis(K) == basis(A) \ No newline at end of file diff --git a/test/test_superop.jl b/test/test_superop.jl index d000c14..f9afe8a 100644 --- a/test/test_superop.jl +++ b/test/test_superop.jl @@ -47,9 +47,9 @@ noisy_pair_fromdm = (noiseop ⊗ noiseop) * pure_pair_dm @op A; @op B; @op C; @op O; @ket k; @superop S; K = kraus(A, B, C); - - @testset "symbolic superoperator tests" begin + @test ishermitian(S) == false + @test isunitary(S) == false @test isequal(S*SZeroOperator(), SZeroOperator()) @test isequal(S*SZeroKet(), SZeroOperator()) @test isequal(S*k, S*projector(k)) diff --git a/test/test_throws.jl b/test/test_throws.jl new file mode 100644 index 0000000..fec1ca3 --- /dev/null +++ b/test/test_throws.jl @@ -0,0 +1,17 @@ +using Test +using QuantumSymbolics +using QuantumOptics +using QuantumInterface: IncompatibleBases + +@op A SpinBasis(1//2) ⊗ SpinBasis(1//2); @op B; @op C; +@ket k; @bra b; @ket l SpinBasis(1//2) ⊗ SpinBasis(1//2); + +@test_throws IncompatibleBases A*B +@test_throws IncompatibleBases commutator(A, B) +@test_throws IncompatibleBases anticommutator(A, B) +@test_throws IncompatibleBases A*k +@test_throws IncompatibleBases b*A +@test_throws IncompatibleBases l*b +@test_throws IncompatibleBases b*l +@test_throws ArgumentError ptrace(B, 2) +@test_throws ArgumentError ptrace(B+C, 2) \ No newline at end of file diff --git a/test/test_trace.jl b/test/test_trace.jl index ef4f59a..746da93 100644 --- a/test/test_trace.jl +++ b/test/test_trace.jl @@ -24,13 +24,11 @@ exp2 = A⊗(B⊗C + D⊗E) @test isequal(ptrace(𝒪, 2), SPartialTrace(𝒪, 2)) @test isequal(ptrace(A, 1), tr(A)) @test isequal(ptrace(A*(B+C), 1), tr(A*B)+tr(A*C)) - @test_throws ArgumentError ptrace(A, 2) @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 1)), SpinBasis(1//2)) @test isequal(QuantumSymbolics.basis(ptrace(𝒪, 2)), SpinBasis(1//2)) # tests for ptrace(x::SAddOperator, s) @test isequal(ptrace(A+B, 1), tr(A+B)) - @test_throws ArgumentError ptrace(A+B, 2) @test isequal(ptrace(2*(A⊗B)+(C⊗D), 1), 2*tr(A)*B + tr(C)*D) @test isequal(ptrace((A⊗B)+(C⊗D), 1), tr(A)*B + tr(C)*D) @test isequal(ptrace((A⊗B⊗C)+(D⊗E⊗F), 1), tr(A)*(B⊗C) + tr(D)*(E⊗F)) From 9f05adbf0e1708b7eb4f5fc5197b1c02f15483d9 Mon Sep 17 00:00:00 2001 From: apkille Date: Fri, 19 Jul 2024 14:01:36 -0400 Subject: [PATCH 22/22] update changelog and project.toml --- CHANGELOG.md | 5 +++++ Project.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4978657..ff068e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # News +## v0.3.4 - dev + +- Added `tr` and `ptrace` functionalities. +- New symbolic superoperator representations. + ## v0.3.3 - 2024-07-12 - Added single qubit simplification rules. diff --git a/Project.toml b/Project.toml index 58d45f7..fcd3a93 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuantumSymbolics" uuid = "efa7fd63-0460-4890-beb7-be1bbdfbaeae" authors = ["QuantumSymbolics.jl contributors"] -version = "0.3.3" +version = "0.3.4-dev" [deps] Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"