From 0f47b58058e6ef5a8e00473759fd75064ca092e2 Mon Sep 17 00:00:00 2001 From: Andrew Kille <68079167+apkille@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:07:40 -0400 Subject: [PATCH] Addresses #46 and fixes #47 (#50) * add zero structs * set fixes * hash method * isequal and idiomatic changes * generator comp for dagger * more generators and fix * organize SAdd * doctest fix * change JET threshold to 7 * new version updates --- CHANGELOG.md | 5 +- Project.toml | 2 +- src/QSymbolicsBase/QSymbolicsBase.jl | 24 ++++----- src/QSymbolicsBase/basic_ops_homogeneous.jl | 49 ++++++++++++++----- src/QSymbolicsBase/basic_ops_inhomogeneous.jl | 17 +++++++ src/QSymbolicsBase/literal_objects.jl | 20 +++++++- src/QSymbolicsBase/predefined.jl | 14 +++--- src/extensions.jl | 3 +- test/runtests.jl | 1 + test/test_commutator.jl | 2 +- test/test_zero_obj.jl | 39 +++++++++++++++ 11 files changed, 135 insertions(+), 41 deletions(-) create mode 100644 test/test_zero_obj.jl diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4e9bb..38ea614 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # News -## v0.3.1 - dev +## v0.3.1 - 2024-06-21 - Macros for defining symbolic quantum objects. +- Implement zero objects. +- Equality for commutative operations, hashing, and lexicographic ordering when printing. +- Added tests. ## v0.3.0 - 2024-06-12 diff --git a/Project.toml b/Project.toml index 062adf0..90372d4 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.1-dev" +version = "0.3.1" [deps] Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" diff --git a/src/QSymbolicsBase/QSymbolicsBase.jl b/src/QSymbolicsBase/QSymbolicsBase.jl index 7754229..ac50922 100644 --- a/src/QSymbolicsBase/QSymbolicsBase.jl +++ b/src/QSymbolicsBase/QSymbolicsBase.jl @@ -34,7 +34,8 @@ export SymQObj,QObj, SAdd,SAddBra,SAddKet,SAddOperator, SScaled,SScaledBra,SScaledOperator,SScaledKet, STensorBra,STensorKet,STensorOperator, - SProjector,MixedState,IdentityOp,SInvOperator, + SZeroBra,SZeroKet,SZeroOperator, + 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, @@ -138,25 +139,15 @@ const QObj = Union{AbstractBra,AbstractKet,AbstractOperator,AbstractSuperOperato 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) +Base.hash(x::SymQObj, h::UInt) = isexpr(x) ? hash((head(x), arguments(x)), h) : +hash((typeof(x),symbollabel(x),basis(x)), h) -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}}} +function Base.isequal(x::X,y::Y) where {X<:SymQObj, Y<:SymQObj} if X==Y if isexpr(x) if operation(x)==operation(y) ax,ay = arguments(x),arguments(y) - if (operation(x) === +) && (length(ax) == length(ay)) - all(x -> _in(x, y), ax) - else - all(zip(ax,ay)) do xy isequal(xy...) end - end + (operation(x) === +) ? x._set_precomputed == y._set_precomputed : all(zip(ax,ay)) do xy isequal(xy...) end else false end @@ -167,6 +158,9 @@ function Base.isequal(x::X,y::Y) where {X<:Union{SymQObj, Symbolic{Complex}}, Y< false end end +Base.isequal(::SymQObj, ::Symbolic{Complex}) = false +Base.isequal(::Symbolic{Complex}, ::SymQObj) = false + # TODO check that this does not cause incredibly bad runtime performance # use a macro to provide specializations if that is indeed the case diff --git a/src/QSymbolicsBase/basic_ops_homogeneous.jl b/src/QSymbolicsBase/basic_ops_homogeneous.jl index 0c96023..176feeb 100644 --- a/src/QSymbolicsBase/basic_ops_homogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_homogeneous.jl @@ -1,5 +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 @@ -16,7 +17,7 @@ A julia> 2*A 2A -```` +``` """ @withmetadata struct SScaled{T<:QObj} <: Symbolic{T} coeff @@ -29,9 +30,9 @@ 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.:(/)(x::Symbolic{T}, c) where {T<:QObj} = SScaled{T}(1/c,x) +Base.:(*)(c, x::Symbolic{T}) where {T<:QObj} = iszero(c) || iszero(x) ? SZero{T}() : SScaled{T}(c, x) +Base.:(*)(x::Symbolic{T}, c) where {T<:QObj} = c*x +Base.:(/)(x::Symbolic{T}, c) where {T<:QObj} = iszero(c) ? throw(DomainError(c,"cannot divide QSymbolics expressions by zero")) : (1/c)*x basis(x::SScaled) = basis(x.obj) const SScaledKet = SScaled{AbstractKet} @@ -70,15 +71,25 @@ julia> 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) + _set_precomputed + _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) end isexpr(::SAdd) = true iscall(::SAdd) = true -arguments(x::SAdd) = [SScaledKet(v,k) for (k,v) in pairs(x.dict)] +arguments(x::SAdd) = x._arguments_precomputed operation(x::SAdd) = + head(x::SAdd) = :+ -children(x::SAdd) = [:+,SScaledKet(v,k) for (k,v) in pairs(x.dict)] -Base.:(+)(xs::Vararg{Symbolic{T},N}) where {T<:QObj,N} = SAdd{T}(countmap_flatten(xs, SScaled{T})) +children(x::SAdd) = [:+; x._arguments_precomputed] +function Base.:(+)(xs::Vararg{Symbolic{T},N}) where {T<:QObj,N} + xs = collect(xs) + f = first(xs) + nonzero_terms = filter!(x->!iszero(x),xs) + isempty(nonzero_terms) ? f : SAdd{T}(countmap_flatten(nonzero_terms, SScaled{T})) +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) @@ -120,7 +131,10 @@ arguments(x::SMulOperator) = x.terms operation(x::SMulOperator) = * head(x::SMulOperator) = :* children(x::SMulOperator) = [:*;x.terms] -Base.:(*)(xs::Symbolic{AbstractOperator}...) = SMulOperator(collect(xs)) +function Base.:(*)(xs::Symbolic{AbstractOperator}...) + zero_ind = findfirst(x->iszero(x), xs) + isnothing(zero_ind) ? SMulOperator(collect(xs)) : SZeroOperator() +end Base.show(io::IO, x::SMulOperator) = print(io, join(map(string, arguments(x)),"")) basis(x::SMulOperator) = basis(x.terms) @@ -151,7 +165,10 @@ arguments(x::STensor) = x.terms operation(x::STensor) = ⊗ head(x::STensor) = :⊗ children(x::STensor) = pushfirst!(x.terms,:⊗) -⊗(xs::Symbolic{T}...) where {T<:QObj} = STensor{T}(collect(xs)) +function ⊗(xs::Symbolic{T}...) where {T<:QObj} + zero_ind = findfirst(x->iszero(x), xs) + isnothing(zero_ind) ? STensor{T}(collect(xs)) : SZero{T}() +end basis(x::STensor) = tensor(basis.(x.terms)...) const STensorKet = STensor{AbstractKet} @@ -172,7 +189,7 @@ julia> commutator(A, B) [A,B] julia> commutator(A, A) -0 +𝟎 ``` """ @withmetadata struct SCommutator <: Symbolic{AbstractOperator} @@ -180,7 +197,7 @@ julia> commutator(A, A) op2 function SCommutator(o1, o2) coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) - cleanterms[1] === cleanterms[2] ? 0 : coeff*new(cleanterms...) + cleanterms[1] === cleanterms[2] ? SZeroOperator() : coeff*new(cleanterms...) end end isexpr(::SCommutator) = true @@ -190,6 +207,9 @@ 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) +commutator(o1::SZeroOperator, o2::Symbolic{AbstractOperator}) = SZeroOperator() +commutator(o1::Symbolic{AbstractOperator}, o2::SZeroOperator) = SZeroOperator() +commutator(o1::SZeroOperator, o2::SZeroOperator) = SZeroOperator() 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 @@ -218,6 +238,9 @@ 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) +anticommutator(o1::SZeroOperator, o2::Symbolic{AbstractOperator}) = SZeroOperator() +anticommutator(o1::Symbolic{AbstractOperator}, o2::SZeroOperator) = SZeroOperator() +anticommutator(o1::SZeroOperator, o2::SZeroOperator) = SZeroOperator() 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 2e83277..49c48ae 100644 --- a/src/QSymbolicsBase/basic_ops_inhomogeneous.jl +++ b/src/QSymbolicsBase/basic_ops_inhomogeneous.jl @@ -26,6 +26,9 @@ operation(x::SApplyKet) = * head(x::SApplyKet) = :* children(x::SApplyKet) = [:*,x.op,x.ket] Base.:(*)(op::Symbolic{AbstractOperator}, k::Symbolic{AbstractKet}) = SApplyKet(op,k) +Base.:(*)(op::SZeroOperator, k::Symbolic{AbstractKet}) = SZeroKet() +Base.:(*)(op::Symbolic{AbstractOperator}, k::SZeroKet) = SZeroKet() +Base.:(*)(op::SZeroOperator, k::SZeroKet) = SZeroKet() Base.show(io::IO, x::SApplyKet) = begin print(io, x.op); print(io, x.ket) end basis(x::SApplyKet) = basis(x.ket) @@ -36,6 +39,7 @@ julia> @bra b; @op A; julia> b*A ⟨b|A +``` """ @withmetadata struct SApplyBra <: Symbolic{AbstractBra} bra @@ -52,6 +56,9 @@ operation(x::SApplyBra) = * head(x::SApplyBra) = :* children(x::SApplyBra) = [:*,x.bra,x.op] Base.:(*)(b::Symbolic{AbstractBra}, op::Symbolic{AbstractOperator}) = SApplyBra(b,op) +Base.:(*)(b::SZeroBra, op::Symbolic{AbstractOperator}) = SZeroBra() +Base.:(*)(b::Symbolic{AbstractBra}, op::SZeroOperator) = SZeroBra() +Base.:(*)(b::SZeroBra, op::SZeroOperator) = SZeroBra() Base.show(io::IO, x::SApplyBra) = begin print(io, x.bra); print(io, x.op) end basis(x::SApplyBra) = basis(x.bra) @@ -75,7 +82,11 @@ operation(x::SBraKet) = * head(x::SBraKet) = :* children(x::SBraKet) = [:*,x.bra,x.ket] Base.:(*)(b::Symbolic{AbstractBra}, k::Symbolic{AbstractKet}) = SBraKet(b,k) +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.isequal(x::SBraKet, y::SBraKet) = isequal(x.bra, y.bra) && isequal(x.ket, y.ket) """Symbolic application of a superoperator on an operator""" @withmetadata struct SSuperOpApply <: Symbolic{AbstractOperator} @@ -89,7 +100,9 @@ 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) = SZeroKet() Base.show(io::IO, x::SSuperOpApply) = begin print(io, x.sop); print(io, x.op) end basis(x::SSuperOpApply) = basis(x.op) @@ -99,6 +112,7 @@ julia> @bra b; @ket k; julia> k*b |k⟩⟨b| +``` """ @withmetadata struct SOuterKetBra <: Symbolic{AbstractOperator} ket @@ -115,5 +129,8 @@ 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::SZeroKet, b::Symbolic{AbstractBra}) = SZeroOperator() +Base.:(*)(k::Symbolic{AbstractKet}, b::SZeroBra) = SZeroOperator() +Base.:(*)(k::SZeroKet, b::SZeroBra) = SZeroOperator() 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/literal_objects.jl b/src/QSymbolicsBase/literal_objects.jl index 255df40..d6ebfbc 100644 --- a/src/QSymbolicsBase/literal_objects.jl +++ b/src/QSymbolicsBase/literal_objects.jl @@ -67,7 +67,7 @@ SHermitianUnitaryOperator(name) = SHermitianUnitaryOperator(name, qubit_basis) ishermitian(::SHermitianUnitaryOperator) = true isunitary(::SHermitianUnitaryOperator) = true -const SymQ = Union{SKet, SBra, SOperator, SHermitianOperator, SUnitaryOperator, SHermitianUnitaryOperator} +const SymQ = Union{SKet,SBra,SOperator,SHermitianOperator,SUnitaryOperator,SHermitianUnitaryOperator} isexpr(::SymQ) = false metadata(::SymQ) = nothing symbollabel(x::SymQ) = x.name @@ -75,5 +75,21 @@ 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}) = 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 + +const SZeroBra = SZero{AbstractBra} + +const SZeroKet = SZero{AbstractKet} + +const SZeroOperator = SZero{AbstractOperator} + +isexpr(::SZero) = false +metadata(::SZero) = nothing +symbollabel(x::SZero) = "𝟎" +basis(x::SZero) = nothing + +Base.show(io::IO, x::SZero) = print(io, symbollabel(x)) +Base.iszero(x::SymQObj) = isa(x, SZero) diff --git a/src/QSymbolicsBase/predefined.jl b/src/QSymbolicsBase/predefined.jl index 76e4e71..182e8bc 100644 --- a/src/QSymbolicsBase/predefined.jl +++ b/src/QSymbolicsBase/predefined.jl @@ -222,6 +222,7 @@ 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,"𝐏[") @@ -264,20 +265,19 @@ 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))) +dagger(x::SAdd) = (+)((dagger(i) for i in arguments(x))...) 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::SZeroOperator) = x 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::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([dagger(i) for i in reverse(x.terms)]) +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 diff --git a/src/extensions.jl b/src/extensions.jl index 80aceea..cbc8654 100644 --- a/src/extensions.jl +++ b/src/extensions.jl @@ -23,6 +23,7 @@ Ket(dim=2) end isexpr(::StabilizerState) = false basis(x::StabilizerState) = SpinBasis(1//2)^nqubits(x.stabilizer) -Base.show(io::IO, x::StabilizerState) = print(io, "𝒮$(num_to_sub(nqubits(x.stabilizer)))") +symbollabel(x::StabilizerState) = "𝒮$(num_to_sub(nqubits(x.stabilizer)))" +Base.show(io::IO, x::StabilizerState) = print(io, symbollabel(x)) StabilizerState(s::T) where {T} = StabilizerState{T}(s) # TODO this is necessary because the @withmetadata macro is not very smart diff --git a/test/runtests.jl b/test/runtests.jl index f34261c..bee2e0d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -34,6 +34,7 @@ println("Starting tests with $(Threads.nthreads()) threads out of `Sys.CPU_THREA @doset "commutator" @doset "anticommutator" @doset "dagger" +@doset "zero_obj" VERSION >= v"1.9" && @doset "doctests" get(ENV,"JET_TEST","")=="true" && @doset "jet" diff --git a/test/test_commutator.jl b/test/test_commutator.jl index be938a5..23d7520 100644 --- a/test/test_commutator.jl +++ b/test/test_commutator.jl @@ -5,7 +5,7 @@ using Test @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 + @test commutator(A, A) == SZeroOperator() end @testset "commutator Pauli tests" begin diff --git a/test/test_zero_obj.jl b/test/test_zero_obj.jl new file mode 100644 index 0000000..2225297 --- /dev/null +++ b/test/test_zero_obj.jl @@ -0,0 +1,39 @@ +using QuantumSymbolics +using Test + +A = SOperator(:A, SpinBasis(1//2)) +k = SKet(:k, SpinBasis(1//2)) +b = SBra(:b, SpinBasis(1//2)) + +Oop = SZeroOperator() +Ok = SZeroKet() +Ob = SZeroBra() + +@testset "zero operator tests" begin + @test isequal(0*A, Oop) && isequal(A*0, Oop) + @test isequal(2*Oop, Oop) && isequal(Oop*2, Oop) && isequal(Oop/2, Oop) + @test isequal(Oop + A, A) && isequal(A + Oop, A) && isequal(Oop + Oop, Oop) + @test isequal(Oop*A, Oop) && isequal(A*Oop, Oop) + @test isequal(Oop ⊗ A, Oop) && isequal(A ⊗ Oop, Oop) && isequal(Oop*Oop, Oop) + @test isequal(commutator(A, Oop), Oop) && isequal(commutator(Oop, A), Oop) && isequal(commutator(Oop, Oop), Oop) + @test isequal(anticommutator(A, Oop), Oop) && isequal(anticommutator(Oop, A), Oop) && isequal(anticommutator(Oop, Oop), Oop) + @test isequal(projector(Ok), Oop) + @test isequal(dagger(Oop), Oop) +end + +@testset "zero bra and ket tests" begin + @test isequal(0*k, Ok) && isequal(k*0, Ok) + @test isequal(2*Ok, Ok) && isequal(Ok*2, Ok) && isequal(Ok/2, Ok) + @test isequal(Ok + k, k) && isequal(k + Ok, k) && isequal(Ok + Ok, Ok) + @test isequal(Ok ⊗ k, Ok) && isequal(k ⊗ Ok, Ok) + @test isequal(0*b, Ob) && isequal(b*0, Ob) + @test isequal(2*Ob, Ob) && isequal(Ob*2, Ob) && isequal(Ob/2, Ob) + @test isequal(Ob + b, b) && isequal(b + Ob, b) && isequal(Ob + Ob, Ob) + @test isequal(Ob ⊗ b, Ob) && isequal(b ⊗ Ob, Ob) + @test isequal(Oop*k, Ok) && isequal(A*Ok, Ok) && isequal(Oop*Ok, Ok) + @test isequal(b*Oop, Ob) && isequal(Ob*A, Ob) && isequal(Ob*Oop, Ob) + @test isequal(Ok*b, Oop) && isequal(k*Ob, Oop) && isequal(Ok*Ob, Oop) + @test isequal(Ob*k, 0) && isequal(b*Ok, 0) && isequal(Ob*Ok, 0) +end + +