diff --git a/.gitignore b/.gitignore index ba39cc5..2ba67bd 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ Manifest.toml +.vscode \ No newline at end of file diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index e4a1eb1..5a4f490 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -6,7 +6,7 @@ using TermInterface import TermInterface: isexpr, head, iscall, children, operation, arguments, metadata using LinearAlgebra -import LinearAlgebra: eigvecs +import LinearAlgebra: eigvecs, ishermitian, inv import QuantumInterface: apply!, @@ -17,24 +17,30 @@ 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, - X,Y,Z,σˣ,σʸ,σᶻ,Pm,Pp,σ₋,σ₊, + 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,â, - SProjector,MixedState,IdentityOp, - STensorKet,STensorOperator,SScaledKet,SScaledOperator,SAddKet,SAddOperator,SScaledBra,SAddBra,STensorBra,SDagger, - HGate, XGate, YGate, ZGate, CPHASEGate, CNOTGate, - XBasisState, YBasisState, ZBasisState, - NumberOp, CreateOp, DestroyOp, - XCXGate, XCYGate, XCZGate, YCXGate, YCYGate, YCZGate, ZCXGate, ZCYGate, ZCZGate + N,n̂,Create,âꜛ,Destroy,â,SpinBasis,FockBasis, + SBra,SKet,SOperator, + SAdd,SAddBra,SAddKet,SAddOperator, + SScaled,SScaledBra,SScaledOperator,SScaledKet, + STensorBra,STensorKet,STensorOperator, + SProjector,MixedState,IdentityOp,SInvOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator, + 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 counts = Dict{Any,Any}() @@ -127,17 +133,29 @@ newwithmetadata(x) = x # Basic Types ## -const QObj = Union{AbstractKet,AbstractOperator,AbstractSuperOperator,AbstractBra} +const QObj = Union{AbstractBra,AbstractKet,AbstractOperator,AbstractSuperOperator} const SymQObj = Symbolic{<:QObj} # TODO Should we use Sym or Symbolic... Sym has a lot of predefined goodies, including metadata support Base.:(-)(x::SymQObj) = (-1)*x Base.:(-)(x::SymQObj,y::SymQObj) = x + (-y) -function Base.isequal(x::X,y::Y) where {X<:SymQObj, Y<:SymQObj} +function _in(x::SymQObj, y::SymQObj) + for i in arguments(y) + if isequal(x, i) + return true + end + end + false +end +function Base.isequal(x::X,y::Y) where {X<:Union{SymQObj, Symbolic{Complex}}, Y<:Union{SymQObj, Symbolic{Complex}}} if X==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) === +) && (length(ax) == length(ay)) + all(x -> _in(x, y), ax) + else + all(zip(ax,ay)) do xy isequal(xy...) end + end else false end @@ -151,12 +169,13 @@ end # TODO check that this does not cause incredibly bad runtime performance # use a macro to provide specializations if that is indeed the case -propsequal(x,y) = all(n->getproperty(x,n)==getproperty(y,n), propertynames(x)) +propsequal(x,y) = all(n->isequal(getproperty(x,n),getproperty(y,n)), propertynames(x)) ## # Most symbolic objects defined here ## +include("literal_objects.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 9c7ef7d..60fb88a 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -1,24 +1,23 @@ -"""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. +## -struct SKet <: Symbolic{AbstractKet} - name::Symbol - basis::Basis -end -struct SOperator <: Symbolic{AbstractKet} - name::Symbol - basis::Basis -end -const SymQ = Union{SKet, SOperator} -isexpr(::SymQ) = false -metadata(::SymQ) = nothing -basis(x::SymQ) = x.basis +"""Scaling of a quantum object (ket, operator, or bra) by a number + +```jldoctest +julia> k = SKet(:k, SpinBasis(1//2)) +|k⟩ -symbollabel(x::SymQ) = x.name -Base.show(io::IO, x::SKet) = print(io, "|$(symbollabel(x))⟩") -Base.show(io::IO, x::SOperator) = print(io, "$(symbollabel(x))") -Base.show(io::IO, x::SymQObj) = print(io, symbollabel(x)) # fallback that probably is not great +julia> 2*k +2|k⟩ -"""Scaling of a quantum object (ket, operator, or bra) by a number.""" +julia> A = SOperator(:A, SpinBasis(1//2)) +A + +julia> 2*A +2A +```` +""" @withmetadata struct SScaled{T<:QObj} <: Symbolic{T} coeff obj @@ -54,13 +53,21 @@ end const SScaledBra = SScaled{AbstractBra} function Base.show(io::IO, x::SScaledBra) if x.coeff isa Number - print(io, "$(x.obj)$(x.coeff)") + print(io, "$(x.coeff)$(x.obj)") else - print(io, "$(x.obj)($(x.coeff))") + print(io, "($(x.coeff))$(x.obj)") end end -"""Addition of quantum objects (kets, operators, or bras).""" +"""Addition of quantum objects (kets, operators, or bras) + +```jldoctest +julia> k₁ = SKet(:k₁, SpinBasis(1//2)); k₂ = SKet(:k₂, SpinBasis(1//2)); + +julia> k₁ + k₂ +(|k₁⟩+|k₂⟩) +``` +""" @withmetadata struct SAdd{T<:QObj} <: Symbolic{T} dict SAdd{S}(d) where S = length(d)==1 ? SScaled{S}(reverse(first(d))...) : new{S}(d) @@ -76,13 +83,61 @@ Base.:(+)(xs::Vararg{Symbolic{<:QObj},0}) = 0 # to avoid undefined type paramete basis(x::SAdd) = basis(first(x.dict).first) const SAddKet = SAdd{AbstractKet} -Base.show(io::IO, x::SAddKet) = print(io, "("*join(map(string, arguments(x)),"+")::String*")") # type assert to help inference +function Base.show(io::IO, x::SAddKet) + ordered_terms = sort([repr(i) for i in arguments(x)]) + print(io, "("*join(ordered_terms,"+")::String*")") # type assert to help inference +end const SAddOperator = SAdd{AbstractOperator} -Base.show(io::IO, x::SAddOperator) = print(io, "("*join(map(string, arguments(x)),"+")::String*")") # type assert to help inference +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} -Base.show(io::IO, x::SAddBra) = print(io, "("*join(map(string, arguments(x)),"+")::String*")") # type assert to help inference +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 + +```jldoctest +julia> A = SOperator(:A, SpinBasis(1//2)); B = SOperator(:B, SpinBasis(1//2)); -"""Tensor product of quantum objects (kets, operators, or bras).""" +julia> A*B +AB +``` +""" +@withmetadata struct SMulOperator <: Symbolic{AbstractOperator} + terms + function SMulOperator(terms) + coeff, cleanterms = prefactorscalings(terms) + coeff*new(cleanterms) + end +end +isexpr(::SMulOperator) = true +iscall(::SMulOperator) = true +arguments(x::SMulOperator) = x.terms +operation(x::SMulOperator) = * +head(x::SMulOperator) = :* +children(x::SMulOperator) = [:*;x.terms] +Base.:(*)(xs::Symbolic{AbstractOperator}...) = SMulOperator(collect(xs)) +Base.show(io::IO, x::SMulOperator) = print(io, join(map(string, arguments(x)),"")) +basis(x::SMulOperator) = basis(x.terms) + +"""Tensor product of quantum objects (kets, operators, or bras) + +```jldoctest +julia> k₁ = SKet(:k₁, SpinBasis(1//2)); k₂ = SKet(:k₂, SpinBasis(1//2)); + +julia> k₁ ⊗ k₂ +|k₁⟩|k₂⟩ + +julia> A = SOperator(:A, SpinBasis(1//2)); B = SOperator(:B, SpinBasis(1//2)); + +julia> A ⊗ B +A⊗B +``` +""" @withmetadata struct STensor{T<:QObj} <: Symbolic{T} terms function STensor{S}(terms) where S @@ -107,3 +162,62 @@ 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 + +```jldoctest +julia> A = SOperator(:A, SpinBasis(1//2)); B = SOperator(:B, SpinBasis(1//2)); + +julia> commutator(A, B) +[A,B] + +julia> commutator(A, A) +0 +``` +""" +@withmetadata struct SCommutator <: Symbolic{AbstractOperator} + op1 + op2 + function SCommutator(o1, o2) + coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) + cleanterms[1] === cleanterms[2] ? 0 : coeff*new(cleanterms...) + end +end +isexpr(::SCommutator) = true +iscall(::SCommutator) = true +arguments(x::SCommutator) = [x.op1, x.op2] +operation(x::SCommutator) = commutator +head(x::SCommutator) = :commutator +children(x::SCommutator) = [:commutator, x.op1, x.op2] +commutator(o1::Symbolic{AbstractOperator}, o2::Symbolic{AbstractOperator}) = SCommutator(o1, o2) +Base.show(io::IO, x::SCommutator) = print(io, "[$(x.op1),$(x.op2)]") +basis(x::SCommutator) = basis(x.op1) +expand(x::SCommutator) = x == 0 ? x : x.op1*x.op2 - x.op2*x.op1 + +"""Symbolic anticommutator of two operators + +```jldoctest +julia> A = SOperator(:A, SpinBasis(1//2)); B = SOperator(:B, SpinBasis(1//2)); + +julia> anticommutator(A, B) +{A,B} +``` +""" +@withmetadata struct SAnticommutator <: Symbolic{AbstractOperator} + op1 + op2 + function SAnticommutator(o1, o2) + coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) + coeff*new(cleanterms...) + end +end +isexpr(::SAnticommutator) = true +iscall(::SAnticommutator) = true +arguments(x::SAnticommutator) = [x.op1, x.op2] +operation(x::SAnticommutator) = anticommutator +head(x::SAnticommutator) = :anticommutator +children(x::SAnticommutator) = [:anticommutator, x.op1, x.op2] +anticommutator(o1::Symbolic{AbstractOperator}, o2::Symbolic{AbstractOperator}) = SAnticommutator(o1, o2) +Base.show(io::IO, x::SAnticommutator) = print(io, "{$(x.op1),$(x.op2)}") +basis(x::SAnticommutator) = basis(x.op1) +expand(x::SAnticommutator) = x == 0 ? x : x.op1*x.op2 + x.op2*x.op1 diff --git a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl index e657d34..25a3e04 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -1,9 +1,23 @@ -"""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)""" +"""Symbolic application of an operator on a ket (from the left) + +```jldoctest +julia> k = SKet(:k, SpinBasis(1//2)); A = SOperator(:A, SpinBasis(1//2)); + +julia> A*k +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 @@ -15,10 +29,21 @@ Base.:(*)(op::Symbolic{AbstractOperator}, k::Symbolic{AbstractKet}) = SApplyKet( Base.show(io::IO, x::SApplyKet) = begin print(io, x.op); print(io, x.ket) end basis(x::SApplyKet) = basis(x.ket) -"""Symbolic application of an operator on a bra (from the right)""" +"""Symbolic application of an operator on a bra (from the right) + +```jldoctest +julia> b = SBra(:b, SpinBasis(1//2)); A = SOperator(:A, SpinBasis(1//2)); + +julia> b*A +⟨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 @@ -30,7 +55,15 @@ Base.:(*)(b::Symbolic{AbstractBra}, op::Symbolic{AbstractOperator}) = SApplyBra( Base.show(io::IO, x::SApplyBra) = begin print(io, x.bra); print(io, x.op) end basis(x::SApplyBra) = basis(x.bra) -"""Symbolic inner product of a bra and a ket.""" +"""Symbolic inner product of a bra and a ket + +```jldoctest +julia> b = SBra(:b, SpinBasis(1//2)); k = SKet(:k, SpinBasis(1//2)); + +julia> b*k +⟨b||k⟩ +``` +""" @withmetadata struct SBraKet <: Symbolic{Complex} bra ket @@ -42,31 +75,38 @@ 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.show(io::IO, x::SBraKet) - print(io,x.bra) - print(io,x.ket) -end +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 SApplyOp <: Symbolic{AbstractOperator} +@withmetadata struct SSuperOpApply <: Symbolic{AbstractOperator} sop op end -isexpr(::SApplyOp) = true -iscall(::SApplyOp) = true -arguments(x::SApplyOp) = [x.sop,x.op] -operation(x::SApplyOp) = * -head(x::SApplyOp) = :* -children(x::SApplyOp) = [:*,x.sop,x.op] -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, op::Symbolic{AbstractOperator}) = SApplyOp(sop,op) -Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::Symbolic{AbstractKet}) = SApplyOp(sop,SProjector(k)) -Base.show(io::IO, x::SApplyOp) = begin print(io, x.sop); print(io, x.op) end -basis(x::SApplyOp) = 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 +julia> b = SBra(:b, SpinBasis(1//2)); k = SKet(:k, SpinBasis(1//2)); -"""Symbolic outer product of a ket and a bra""" +julia> k*b +|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 @@ -75,8 +115,5 @@ operation(x::SOuterKetBra) = * head(x::SOuterKetBra) = :* children(x::SOuterKetBra) = [:*,x.ket,x.bra] Base.:(*)(k::Symbolic{AbstractKet}, b::Symbolic{AbstractBra}) = SOuterKetBra(k,b) -Base.:(*)(k::SScaledKet, b::Symbolic{AbstractBra}) = k.coeff*SOuterKetBra(k.obj,b) -Base.:(*)(k::Symbolic{AbstractKet}, b::SScaledBra) = b.coeff*SOuterKetBra(k,b.obj) -Base.:(*)(k::SScaledKet, b::SScaledBra) = k.coeff*b.coeff*SOuterKetBra(k.obj,b.obj) Base.show(io::IO, x::SOuterKetBra) = begin print(io, x.ket); print(io, x.bra) end basis(x::SOuterKetBra) = basis(x.ket) 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/literal_objects.jl b/src/QSymbolicsBase/literal_objects.jl new file mode 100644 index 0000000..ad67c49 --- /dev/null +++ b/src/QSymbolicsBase/literal_objects.jl @@ -0,0 +1,52 @@ +## +# This file defines quantum objects (kets, bras, and operators) with various properties +## + +struct SKet <: Symbolic{AbstractKet} + name::Symbol + basis::Basis +end + +struct SBra <: Symbolic{AbstractBra} + name::Symbol + basis::Basis +end + +struct SOperator <: Symbolic{AbstractOperator} + name::Symbol + basis::Basis +end +ishermitian(x::SOperator) = false +isunitary(x::SOperator) = false + +struct SHermitianOperator <: Symbolic{AbstractOperator} + name::Symbol + basis::Basis +end +ishermitian(::SHermitianOperator) = true +isunitary(::SHermitianOperator) = false + +struct SUnitaryOperator <: Symbolic{AbstractOperator} + name::Symbol + basis::Basis +end +ishermitian(::SUnitaryOperator) = false +isunitary(::SUnitaryOperator) = true + +struct SHermitianUnitaryOperator <: Symbolic{AbstractOperator} + name::Symbol + basis::Basis +end +ishermitian(::SHermitianUnitaryOperator) = true +isunitary(::SHermitianUnitaryOperator) = true + +const SymQ = Union{SKet, SBra, SOperator, SHermitianOperator, SUnitaryOperator, SHermitianUnitaryOperator} +isexpr(::SymQ) = false +metadata(::SymQ) = nothing +symbollabel(x::SymQ) = x.name +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::SymQObj) = print(io, symbollabel(x)) # fallback that probably is not great diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 16638a6..fc011d6 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -91,7 +91,6 @@ 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))") - @withmetadata struct OperatorEmbedding <: Symbolic{AbstractOperator} gate::Symbolic{AbstractOperator} # TODO parameterize indices::Vector{Int} @@ -102,22 +101,45 @@ isexpr(::OperatorEmbedding) = true @withmetadata struct XGate <: AbstractSingleQubitGate end eigvecs(g::XGate) = [X1,X2] symbollabel(::XGate) = "X" +ishermitian(::XGate) = true +isunitary(::XGate) = true + @withmetadata struct YGate <: AbstractSingleQubitGate end eigvecs(g::YGate) = [Y1,Y2] symbollabel(::YGate) = "Y" +ishermitian(::YGate) = true +isunitary(::YGate) = true + @withmetadata struct ZGate <: AbstractSingleQubitGate end eigvecs(g::ZGate) = [Z1,Z2] symbollabel(::ZGate) = "Z" +ishermitian(::ZGate) = true +isunitary(::ZGate) = true + @withmetadata struct PauliM <: AbstractSingleQubitGate end symbollabel(::PauliM) = "σ₋" +ishermitian(::PauliM) = true +isunitary(::PauliM) = true + @withmetadata struct PauliP <: AbstractSingleQubitGate end symbollabel(::PauliP) = "σ₊" +ishermitian(::PauliP) = true +isunitary(::PauliP) = true + @withmetadata struct HGate <: AbstractSingleQubitGate end symbollabel(::HGate) = "H" +ishermitian(::HGate) = true +isunitary(::HGate) = true + @withmetadata struct CNOTGate <: AbstractTwoQubitGate end symbollabel(::CNOTGate) = "CNOT" +ishermitian(::CNOTGate) = true +isunitary(::CNOTGate) = true + @withmetadata struct CPHASEGate <: AbstractTwoQubitGate end symbollabel(::CPHASEGate) = "CPHASE" +ishermitian(::CPHASEGate) = true +isunitary(::CPHASEGate) = true const xyzsuplabeldict = Dict(:X=>"ˣ",:Y=>"ʸ",:Z=>"ᶻ") for control in (:X, :Y, :Z) @@ -207,25 +229,91 @@ function Base.show(io::IO, x::SProjector) print(io,"]") end -"""Dagger a Ket into Bra.""" -@withmetadata struct SDagger <: Symbolic{AbstractBra} - ket::Symbolic{AbstractKet} +"""Dagger, i.e., adjoint of quantum objects (kets, bras, operators) + +```jldoctest +julia> a = SKet(:a, SpinBasis(1//2)); A = SOperator(:A, SpinBasis(1//2)); + +julia> dagger(2*im*A*a) +0 - 2im|a⟩†A† + +julia> B = SOperator(:B, SpinBasis(1//2)); + +julia> dagger(A*B) +B†A† + +julia> ℋ = SHermitianOperator(:ℋ, SpinBasis(1//2)); U = SUnitaryOperator(:U, SpinBasis(1//2)); + +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.ket] +arguments(x::SDagger) = [x.obj] operation(x::SDagger) = dagger head(x::SDagger) = :dagger -children(x::SDagger) = [:dagger, x.ket] -dagger(x::Symbolic{AbstractKet}) = SDagger(x) -dagger(x::SScaledKet) = SScaledBra(x.coeff, dagger(x.obj)) +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::SAddKet) = SAddBra(Dict(dagger(k)=>v for (k,v) in pairs(x.dict))) -basis(x::SDagger) = basis(x.ket) +dagger(x::SScaledBra) = SScaledKet(conj(x.coeff), dagger(x.obj)) +dagger(x::SAddBra) = SAddKet(Dict(dagger(b)=>v for (b,v) in pairs(x.dict))) +dagger(x::SAddOperator) = SAddOperator(Dict(dagger(o)=>v for (o,v) in pairs(x.dict))) +dagger(x::SHermitianOperator) = x +dagger(x::SHermitianUnitaryOperator) = x +dagger(x::SUnitaryOperator) = inv(x) +dagger(x::STensorBra) = STensorKet([dagger(i) for i in x.terms]) +dagger(x::STensorKet) = STensorBra([dagger(i) for i in x.terms]) +dagger(x::STensorOperator) = STensorOperator([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([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.ket) + print(io,x.obj) print(io,"†") end -symbollabel(x::SDagger) = symbollabel(x.ket) +symbollabel(x::SDagger) = symbollabel(x.obj) + +"""Inverse Operator + +```jldoctest +julia> A = SOperator(:A, SpinBasis(1//2)); + +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 @@ -276,3 +364,8 @@ IdentityOp(x::Symbolic{AbstractOperator}) = IdentityOp(basis(x)) isexpr(::IdentityOp) = false basis(x::IdentityOp) = x.basis symbollabel(x::IdentityOp) = "𝕀" +ishermitian(::IdentityOp) = true +isunitary(::IdentityOp) = true + +"""Identity operator in qubit basis""" +const I = IdentityOp(qubit_basis) \ No newline at end of file diff --git a/src/QSymbolicsBase/rules.jl b/src/QSymbolicsBase/rules.jl index b2d72b0..6a8d6b7 100644 --- a/src/QSymbolicsBase/rules.jl +++ b/src/QSymbolicsBase/rules.jl @@ -1,18 +1,36 @@ +## +# This file defines automatic simplification rules for specific operations of quantum objects. +## + +## +# Predicate functions +## function hasscalings(xs) any(xs) do x operation(x) == * end end +_isa(T) = x->isa(x,T) + +## +# Determining factors for expressions containing quantum objects +## -""" Used to perform (a*|k⟩) ⊗ (b*|l⟩) → (a*b) * (|k⟩⊗|l⟩) """ -function prefactorscalings(xs) +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 + 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 @@ -38,9 +56,84 @@ 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) ] -tensor_simplify = Fixpoint(Chain(FLATTEN_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), + @rule(~o1::_isa(XGate)*~o2::_isa(YGate) => im*Z), + @rule(~o1::_isa(YGate)*~o2::_isa(ZGate) => im*X), + @rule(~o1::_isa(ZGate)*~o2::_isa(XGate) => im*Y), + @rule(~o1::_isa(YGate)*~o2::_isa(XGate) => -im*Z), + @rule(~o1::_isa(ZGate)*~o2::_isa(YGate) => -im*X), + @rule(~o1::_isa(XGate)*~o2::_isa(ZGate) => -im*Y), + @rule(~o1::_isa(HGate)*~o2::_isa(XGate)*~o3::_isa(HGate) => Z), + @rule(~o1::_isa(HGate)*~o2::_isa(YGate)*~o3::_isa(HGate) => -Y), + @rule(~o1::_isa(HGate)*~o2::_isa(ZGate)*~o3::_isa(HGate) => X) +] + +# 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), + @rule(commutator(~o1::_isa(YGate), ~o2::_isa(XGate)) => -2*im*Z), + @rule(commutator(~o1::_isa(ZGate), ~o2::_isa(YGate)) => -2*im*X), + @rule(commutator(~o1::_isa(XGate), ~o2::_isa(ZGate)) => -2*im*Y) +] + +# 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), + @rule(anticommutator(~o1::_isa(XGate), ~o2::_isa(YGate))=> 0), + @rule(anticommutator(~o1::_isa(YGate), ~o2::_isa(ZGate)) => 0), + @rule(anticommutator(~o1::_isa(ZGate), ~o2::_isa(XGate)) => 0), + @rule(anticommutator(~o1::_isa(YGate), ~o2::_isa(XGate)) => 0), + @rule(anticommutator(~o1::_isa(ZGate), ~o2::_isa(YGate)) => 0), + @rule(anticommutator(~o1::_isa(XGate), ~o2::_isa(ZGate)) => 0) +] + +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 isnothing(rewriter) + 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/runtests.jl b/test/runtests.jl index 2da9b50..f34261c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -31,6 +31,10 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA @doset "basis_consistency" @doset "superop" @doset "conditional_cliffords" +@doset "commutator" +@doset "anticommutator" +@doset "dagger" + VERSION >= v"1.9" && @doset "doctests" get(ENV,"JET_TEST","")=="true" && @doset "jet" VERSION >= v"1.9" && @doset "aqua" diff --git a/test/test_anticommutator.jl b/test/test_anticommutator.jl new file mode 100644 index 0000000..52f9753 --- /dev/null +++ b/test/test_anticommutator.jl @@ -0,0 +1,21 @@ +using QuantumSymbolics +using Test + +A = SOperator(:A, SpinBasis(1//2)) +B = SOperator(:B, SpinBasis(1//2)) + +@testset "symbolic anticommutator tests" begin + @test isequal(anticommutator(2*A, B), anticommutator(A, 2*B)) && isequal(2*anticommutator(A, B), anticommutator(2*A, B)) && isequal(2*anticommutator(A, B), anticommutator(2*A, B)) +end + +@testset "anticommutator Pauli tests" begin + @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 new file mode 100644 index 0000000..3df80da --- /dev/null +++ b/test/test_commutator.jl @@ -0,0 +1,19 @@ +using QuantumSymbolics +using Test + +A = SOperator(:A, SpinBasis(1//2)) +B = SOperator(:B, SpinBasis(1//2)) + +@testset "symbolic commutator tests" begin + @test isequal(commutator(2*A, B), commutator(A, 2*B)) && isequal(2*commutator(A, B), commutator(2*A, B)) && isequal(commutator(A, 2*B), 2*commutator(A, B)) + @test commutator(A, A) == 0 +end + +@testset "commutator Pauli tests" begin + @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 diff --git a/test/test_dagger.jl b/test/test_dagger.jl new file mode 100644 index 0000000..ce740b9 --- /dev/null +++ b/test/test_dagger.jl @@ -0,0 +1,36 @@ +using QuantumSymbolics +using QuantumInterface: AbstractOperator +using Test + +b₁ = SBra(:b₁, SpinBasis(1//2)) +b₂ = SBra(:b₂, SpinBasis(1//2)) +k₁ = SKet(:k₁, SpinBasis(1//2)) +k₂ = SKet(:k₂, SpinBasis(1//2)) + +A = SOperator(:A, SpinBasis(1//2)) +B = SOperator(:B, SpinBasis(1//2)) +C = SOperator(:C, SpinBasis(1//2)) + +U = SUnitaryOperator(:U, SpinBasis(1//2)) +ℋ = SHermitianOperator(:ℋ, SpinBasis(1//2)) + +@testset "symbolic dagger tests" begin + @test isequal(dagger(im*k₁), -im*dagger(k₁)) + @test isequal(dagger(k₁+k₂), dagger(k₁)+dagger(k₂)) + @test isequal(dagger(im*b₁), -im*dagger(b₁)) + @test isequal(dagger(b₁+b₂), dagger(b₁)+dagger(b₂)) + @test isequal(dagger(A+B), dagger(A) + dagger(B)) + @test isequal(dagger(ℋ), ℋ) + @test isequal(dagger(U), inv(U)) + @test isequal(dagger(b₁⊗b₂), dagger(b₁)⊗dagger(b₂)) + @test isequal(dagger(k₁⊗k₂), dagger(k₁)⊗dagger(k₂)) + @test isequal(dagger(A⊗B), dagger(A)⊗dagger(B)) + @test isequal(dagger(im*A), -im*dagger(A)) + @test isequal(dagger(A*k₁), dagger(k₁)*dagger(A)) + @test isequal(dagger(b₁*A), dagger(A)*dagger(b₁)) + @test isequal(dagger(A*B*C), dagger(C)*dagger(B)*dagger(A)) + @test isequal(dagger(b₁*k₁), dagger(k₁)*dagger(b₁)) + @test isequal(dagger(k₁*b₁), dagger(b₁)* dagger(k₁)) + @test isequal(dagger(dagger(A)), A) + @test isequal(dagger(dagger(A)), A) +end \ No newline at end of file diff --git a/test/test_doctests.jl b/test/test_doctests.jl index ea94bc3..8ac845f 100644 --- a/test/test_doctests.jl +++ b/test/test_doctests.jl @@ -2,6 +2,7 @@ using Documenter using QuantumSymbolics using QuantumOptics using QuantumClifford +using Test function doctests() @testset "Doctests" begin