diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index f1859fb..fc6eb3c 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -17,29 +17,29 @@ import QuantumInterface: AbstractKet, AbstractOperator, AbstractSuperOperator, AbstractBra export SymQObj,QObj, - AbstractRepresentation, AbstractUse, - QuantumOpticsRepr, QuantumMCRepr, CliffordRepr, - UseAsState, UseAsObservable, UseAsOperation, + AbstractRepresentation,AbstractUse, + QuantumOpticsRepr,QuantumMCRepr,CliffordRepr, + UseAsState,UseAsObservable,UseAsOperation, apply!, express, tensor,⊗, - dagger,projector,commutator, anticommutator, expand, + dagger,projector,commutator,anticommutator,expand, 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,â,SpinBasis,FockBasis, SBra,SKet,SOperator, SAddBra,SAddKet,SAddOperator, - SScaledBra,SScaledOperator,SScaledKet, + SScaled,SScaledBra,SScaledOperator,SScaledKet, STensorBra,STensorKet,STensorOperator, SProjector,MixedState,IdentityOp,SInvOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator, - SApplyKet,SApplyBra,SMulOperator,SApplySuperop,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, - pauli_simplify, commutator_simplify, anticommutator_simplify, + 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 function countmap(samples) # A simpler version of StatsBase.countmap, because StatsBase is slow to import @@ -143,7 +143,11 @@ function Base.isequal(x::X,y::Y) where {X<:Union{SymQObj, Symbolic{Complex}}, Y< if isexpr(x) if operation(x)==operation(y) ax,ay = arguments(x),arguments(y) - (length(ax) == length(ay)) && all(zip(ax,ay)) do xy isequal(xy...) end + if operation(x) === :+ + Set(ax) == Set(ay) + else + (length(ax) == length(ay)) && all(zip(ax,ay)) do xy isequal(xy...) end + end else false end diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index f1ad1f1..4886010 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -1,4 +1,6 @@ -"""This file defines the symbolic operations for quantum objects (kets, operators, and bras) that are homogeneous in their arguments.""" +## +# This file defines the symbolic operations for quantum objects (kets, operators, and bras) that are homogeneous in their arguments. +## """Scaling of a quantum object (ket, operator, or bra) by a number @@ -27,8 +29,8 @@ arguments(x::SScaled) = [x.coeff,x.obj] operation(x::SScaled) = * head(x::SScaled) = :* children(x::SScaled) = [:*,x.coeff,x.obj] -Base.:(*)(c, x::Symbolic{T}) where {T<:QObj} = SScaled{T}(c,x) -Base.:(*)(x::Symbolic{T}, c) where {T<:QObj} = SScaled{T}(c,x) +Base.:(*)(c, x::Symbolic{T}) where {T<:QObj} = c == 0 ? 0 : SScaled{T}(c,x) +Base.:(*)(x::Symbolic{T}, c) where {T<:QObj} = c == 0 ? 0 : SScaled{T}(c,x) Base.:(/)(x::Symbolic{T}, c) where {T<:QObj} = SScaled{T}(1/c,x) basis(x::SScaled) = basis(x.obj) @@ -87,33 +89,6 @@ Base.show(io::IO, x::SAddOperator) = print(io, "("*join(map(string, arguments(x) const SAddBra = SAdd{AbstractBra} Base.show(io::IO, x::SAddBra) = print(io, "("*join(map(string, arguments(x)),"+")::String*")") # type assert to help inference -# defines commutativity for addition of quantum objects -const CommQObj = Union{SAddBra, SAddKet, SAddOperator} -function _in(x::SymQObj, sadd::CommQObj) - for i in arguments(sadd) - if isequal(x, i) - return true - end - end - false -end -function Base.isequal(x::CommQObj, y::CommQObj) - if typeof(x)==typeof(y) - if isexpr(x) - if operation(x)==operation(y) - ax,ay = arguments(x),arguments(y) - (length(ax) == length(ay)) && all(x -> _in(x, y), ax) - else - false - end - else - propsequal(x,y) # this is unholy - end - else - false - end -end - """Symbolic application of operator on operator ```jldoctest @@ -195,7 +170,7 @@ julia> commutator(A, A) op1 op2 function SCommutator(o1, o2) - coeff, cleanterms = prefactorscalings([o1 o2]) + coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) cleanterms[1] === cleanterms[2] ? 0 : coeff*new(cleanterms...) end end @@ -223,7 +198,7 @@ julia> anticommutator(A, B) op1 op2 function SAnticommutator(o1, o2) - coeff, cleanterms = prefactorscalings([o1 o2]) + coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) coeff*new(cleanterms...) end end diff --git a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl index e90e0af..25a3e04 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -1,4 +1,6 @@ -"""This file defines the symbolic operations for quantum objects (kets, operators, and bras) that are inhomogeneous in their arguments.""" +## +# This file defines the symbolic operations for quantum objects (kets, operators, and bras) that are inhomogeneous in their arguments. +## """Symbolic application of an operator on a ket (from the left) @@ -76,20 +78,20 @@ Base.:(*)(b::Symbolic{AbstractBra}, k::Symbolic{AbstractKet}) = SBraKet(b,k) Base.show(io::IO, x::SBraKet) = begin print(io,x.bra); print(io,x.ket) end """Symbolic application of a superoperator on an operator""" -@withmetadata struct SApplySuperop <: Symbolic{AbstractOperator} +@withmetadata struct SSuperOpApply <: Symbolic{AbstractOperator} sop op end -isexpr(::SApplySuperop) = true -iscall(::SApplySuperop) = true -arguments(x::SApplySuperop) = [x.sop,x.op] -operation(x::SApplySuperop) = * -head(x::SApplySuperop) = :* -children(x::SApplySuperop) = [:*,x.sop,x.op] -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::Symbolic{AbstractOperator}) = SApplySuperop(sop,op) -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::Symbolic{AbstractKet}) = SApplySuperop(sop,SProjector(k)) -Base.show(io::IO, x::SApplySuperop) = begin print(io, x.sop); print(io, x.op) end -basis(x::SApplySuperop) = basis(x.op) +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}, k::Symbolic{AbstractKet}) = SSuperOpApply(sop,SProjector(k)) +Base.show(io::IO, x::SSuperOpApply) = begin print(io, x.sop); print(io, x.op) end +basis(x::SSuperOpApply) = basis(x.op) """Symbolic outer product of a ket and a bra ```jldoctest diff --git a/src/QSymbolicsBase/express.jl b/src/QSymbolicsBase/express.jl index 2e10146..ea983c6 100644 --- a/src/QSymbolicsBase/express.jl +++ b/src/QSymbolicsBase/express.jl @@ -1,6 +1,8 @@ -"""This file defines the expression of quantum objects (kets, operators, and bras) in various representations. - -The main function is `express`, which takes a quantum object and a representation and returns an expression of the object in that representation.""" +## +# This file defines the expression of quantum objects (kets, operators, and bras) in various representations. +# +# The main function is `express`, which takes a quantum object and a representation and returns an expression of the object in that representation. +## export express, express_nolookup, consistent_representation diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index f28dade..ffd934e 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -90,10 +90,6 @@ basis(::AbstractSingleQubitGate) = qubit_basis basis(::AbstractTwoQubitGate) = qubit_basis⊗qubit_basis Base.show(io::IO, x::AbstractSingleQubitOp) = print(io, "$(symbollabel(x))") Base.show(io::IO, x::AbstractTwoQubitOp) = print(io, "$(symbollabel(x))") -Base.:(*)(xs::AbstractSingleQubitGate...) = pauli_simplify(SMulOperator(collect(xs))) -commutator(o1::AbstractSingleQubitGate, o2::AbstractSingleQubitGate) = commutator_simplify(SCommutator(o1, o2)) -anticommutator(o1::AbstractSingleQubitGate, o2::AbstractSingleQubitGate) = anticommutator_simplify(SAnticommutator(o1, o2)) - @withmetadata struct OperatorEmbedding <: Symbolic{AbstractOperator} gate::Symbolic{AbstractOperator} # TODO parameterize diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index 01f0aa2..d5570e2 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -1,6 +1,10 @@ -"""This file defines automatic simplification rules for specific operations of quantum objects""" +## +# This file defines automatic simplification rules for specific operations of quantum objects. +## -"""Predicate functions""" +## +# Predicate functions +## function hasscalings(xs) any(xs) do x operation(x) == * @@ -8,15 +12,25 @@ function hasscalings(xs) end _isa(T) = x->isa(x,T) -"""Flattening terms""" -function prefactorscalings(xs) +## +# 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) - coeff *= c - push!(terms,t) + if scalar == false + coeff *= c + push!(terms,t) + elseif scalar == true && c isa Number + coeff *= c + push!(terms, t) + else + push!(terms,(*)(arguments(x)...)) + end else push!(terms,x) end @@ -42,15 +56,18 @@ function isnotflat_precheck(*) end end -FLATTEN_RULES = [ +## +# Simplification rules +## + +# Flattening expressions +RULES_FLATTEN = [ @rule(~x::isnotflat_precheck(⊗) => flatten_term(⊗, ~x)), - @rule ⊗(~~xs::hasscalings) => prefactorscalings_rule(xs) # Used to perform (a*|k⟩) ⊗ (b*|l⟩) → (a*b) * (|k⟩⊗|l⟩) + @rule ⊗(~~xs::hasscalings) => prefactorscalings_rule(xs) ] -tensor_simplify = Fixpoint(Chain(FLATTEN_RULES)) - -"""Pauli identities""" -PAULI_RULES = [ +# Pauli identities +RULES_PAULI = [ @rule(~o1::_isa(XGate)*~o2::_isa(XGate) => I), @rule(~o1::_isa(YGate)*~o2::_isa(YGate) => I), @rule(~o1::_isa(ZGate)*~o2::_isa(ZGate) => I), @@ -65,10 +82,8 @@ PAULI_RULES = [ @rule(~o1::_isa(HGate)*~o2::_isa(ZGate)*~o3::_isa(HGate) => X) ] -pauli_simplify = Fixpoint(Chain(PAULI_RULES)) - -"""Commutator identities""" -COMMUTATOR_RULES = [ +# Commutator identities +RULES_COMMUTATOR = [ @rule(commutator(~o1::_isa(XGate), ~o2::_isa(YGate)) => 2*im*Z), @rule(commutator(~o1::_isa(YGate), ~o2::_isa(ZGate)) => 2*im*X), @rule(commutator(~o1::_isa(ZGate), ~o2::_isa(XGate)) => 2*im*Y), @@ -77,10 +92,8 @@ COMMUTATOR_RULES = [ @rule(commutator(~o1::_isa(XGate), ~o2::_isa(ZGate)) => -2*im*Y) ] -commutator_simplify = Fixpoint(Chain(COMMUTATOR_RULES)) - -"""Anticommutator identities""" -ANTICOMMUTATOR_RULES = [ +# Anticommutator identities +RULES_ANTICOMMUTATOR = [ @rule(anticommutator(~o1::_isa(XGate), ~o2::_isa(XGate)) => 2*I), @rule(anticommutator(~o1::_isa(YGate), ~o2::_isa(YGate)) => 2*I), @rule(anticommutator(~o1::_isa(ZGate), ~o2::_isa(ZGate)) => 2*I), @@ -92,4 +105,35 @@ ANTICOMMUTATOR_RULES = [ @rule(anticommutator(~o1::_isa(XGate), ~o2::_isa(ZGate)) => 0) ] -anticommutator_simplify = Fixpoint(Chain(ANTICOMMUTATOR_RULES)) \ No newline at end of file +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) + +"""Manually simplify a symbolic expression of quantum objects. + +If the keyword `rewriter` is not specified, then `qsimplify` will apply every defined rule to the expression. +For performance or single-purpose motivations, the user has the option to define a specific rewriter for `qsimplify` to apply to the expression. + +```jldoctest +julia> qsimplify(anticommutator(σˣ, σˣ), rewriter=qsimplify_anticommutator) +2𝕀 +``` +""" +function qsimplify(s; rewriter=nothing) + if QuantumSymbolics.isexpr(s) + if rewriter == nothing + Fixpoint(Chain(RULES_ALL))(s) + else + Fixpoint(rewriter)(s) + end + else + error("Object $(s) of type $(typeof(s)) is not an expression.") + end +end + diff --git a/test/test_anticommutator.jl b/test/test_anticommutator.jl index 2118627..52f9753 100644 --- a/test/test_anticommutator.jl +++ b/test/test_anticommutator.jl @@ -9,13 +9,13 @@ B = SOperator(:B, SpinBasis(1//2)) end @testset "anticommutator Pauli tests" begin - @test isequal(anticommutator(X, X), 2*I) - @test isequal(anticommutator(Y, Y), 2*I) - @test isequal(anticommutator(Z, Z), 2*I) - @test isequal(anticommutator(X, Y), 0) - @test isequal(anticommutator(Y, X), 0) - @test isequal(anticommutator(Y, Z), 0) - @test isequal(anticommutator(Z, Y), 0) - @test isequal(anticommutator(Z, X), 0) - @test isequal(anticommutator(X, Z), 0) + @test isequal(qsimplify(anticommutator(X, X), rewriter=qsimplify_anticommutator), 2*I) + @test isequal(qsimplify(anticommutator(Y, Y), rewriter=qsimplify_anticommutator), 2*I) + @test isequal(qsimplify(anticommutator(Z, Z), rewriter=qsimplify_anticommutator), 2*I) + @test isequal(qsimplify(anticommutator(X, Y), rewriter=qsimplify_anticommutator), 0) + @test isequal(qsimplify(anticommutator(Y, X), rewriter=qsimplify_anticommutator), 0) + @test isequal(qsimplify(anticommutator(Y, Z), rewriter=qsimplify_anticommutator), 0) + @test isequal(qsimplify(anticommutator(Z, Y), rewriter=qsimplify_anticommutator), 0) + @test isequal(qsimplify(anticommutator(Z, X), rewriter=qsimplify_anticommutator), 0) + @test isequal(qsimplify(anticommutator(X, Z), rewriter=qsimplify_anticommutator), 0) end \ No newline at end of file diff --git a/test/test_commutator.jl b/test/test_commutator.jl index c2b7ae5..3df80da 100644 --- a/test/test_commutator.jl +++ b/test/test_commutator.jl @@ -10,11 +10,10 @@ B = SOperator(:B, SpinBasis(1//2)) end @testset "commutator Pauli tests" begin - @test commutator(X, X) == 0 && commutator(Y, Y) == 0 && commutator(Z, Z) == 0 - @test isequal(commutator(X, Y), 2*im*Z) - @test isequal(commutator(Y, X), -2*im*Z) - @test isequal(commutator(Y, Z), 2*im*X) - @test isequal(commutator(Z, Y), -2*im*X) - @test isequal(commutator(Z, X), 2*im*Y) - @test isequal(commutator(X, Z), -2*im*Y) + @test isequal(qsimplify(commutator(X, Y), rewriter=qsimplify_commutator), 2*im*Z) + @test isequal(qsimplify(commutator(Y, X), rewriter=qsimplify_commutator), -2*im*Z) + @test isequal(qsimplify(commutator(Y, Z), rewriter=qsimplify_commutator), 2*im*X) + @test isequal(qsimplify(commutator(Z, Y), rewriter=qsimplify_commutator), -2*im*X) + @test isequal(qsimplify(commutator(Z, X), rewriter=qsimplify_commutator), 2*im*Y) + @test isequal(qsimplify(commutator(X, Z), rewriter=qsimplify_commutator), -2*im*Y) end \ No newline at end of file