Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addresses #46 and fixes #47 #50

Merged
merged 13 commits into from
Jun 21, 2024
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
24 changes: 9 additions & 15 deletions src/QSymbolicsBase/QSymbolicsBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
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,
Expand Down Expand Up @@ -138,25 +139,15 @@
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
Expand All @@ -167,6 +158,9 @@
false
end
end
Base.isequal(::SymQObj, ::Symbolic{Complex}) = false
Base.isequal(::Symbolic{Complex}, ::SymQObj) = false

Check warning on line 162 in src/QSymbolicsBase/QSymbolicsBase.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/QSymbolicsBase.jl#L161-L162

Added lines #L161 - L162 were not covered by tests


# TODO check that this does not cause incredibly bad runtime performance
# use a macro to provide specializations if that is indeed the case
Expand Down
49 changes: 36 additions & 13 deletions src/QSymbolicsBase/basic_ops_homogeneous.jl
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,7 +17,7 @@

julia> 2*A
2A
````
```
"""
@withmetadata struct SScaled{T<:QObj} <: Symbolic{T}
coeff
Expand All @@ -29,9 +30,9 @@
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}
Expand Down Expand Up @@ -70,15 +71,25 @@
"""
@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]

Check warning on line 86 in src/QSymbolicsBase/basic_ops_homogeneous.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/basic_ops_homogeneous.jl#L86

Added line #L86 was not covered by tests
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)

Expand Down Expand Up @@ -120,7 +131,10 @@
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)

Expand Down Expand Up @@ -151,7 +165,10 @@
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}
Expand All @@ -172,15 +189,15 @@
[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...)
cleanterms[1] === cleanterms[2] ? SZeroOperator() : coeff*new(cleanterms...)
end
end
isexpr(::SCommutator) = true
Expand All @@ -190,6 +207,9 @@
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
Expand Down Expand Up @@ -218,6 +238,9 @@
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
17 changes: 17 additions & 0 deletions src/QSymbolicsBase/basic_ops_inhomogeneous.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
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)

Expand All @@ -36,6 +39,7 @@

julia> b*A
⟨b|A
```
"""
@withmetadata struct SApplyBra <: Symbolic{AbstractBra}
bra
Expand All @@ -52,6 +56,9 @@
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)

Expand All @@ -75,7 +82,11 @@
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}
Expand All @@ -89,7 +100,9 @@
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()

Check warning on line 103 in src/QSymbolicsBase/basic_ops_inhomogeneous.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/basic_ops_inhomogeneous.jl#L103

Added line #L103 was not covered by tests
Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::Symbolic{AbstractKet}) = SSuperOpApply(sop,SProjector(k))
Base.:(*)(sop::Symbolic{AbstractSuperOperator}, k::SZeroKet) = SZeroKet()

Check warning on line 105 in src/QSymbolicsBase/basic_ops_inhomogeneous.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/basic_ops_inhomogeneous.jl#L105

Added line #L105 was not covered by tests
Base.show(io::IO, x::SSuperOpApply) = begin print(io, x.sop); print(io, x.op) end
basis(x::SSuperOpApply) = basis(x.op)

Expand All @@ -99,6 +112,7 @@

julia> k*b
|k⟩⟨b|
```
"""
@withmetadata struct SOuterKetBra <: Symbolic{AbstractOperator}
ket
Expand All @@ -115,5 +129,8 @@
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)
20 changes: 18 additions & 2 deletions src/QSymbolicsBase/literal_objects.jl
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,29 @@
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
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

Check warning on line 90 in src/QSymbolicsBase/literal_objects.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/literal_objects.jl#L90

Added line #L90 was not covered by tests
symbollabel(x::SZero) = "𝟎"
basis(x::SZero) = nothing

Check warning on line 92 in src/QSymbolicsBase/literal_objects.jl

View check run for this annotation

Codecov / codecov/patch

src/QSymbolicsBase/literal_objects.jl#L92

Added line #L92 was not covered by tests

Base.show(io::IO, x::SZero) = print(io, symbollabel(x))
Base.iszero(x::SymQObj) = isa(x, SZero)
14 changes: 7 additions & 7 deletions src/QSymbolicsBase/predefined.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,"𝐏[")
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/extensions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion test/test_commutator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions test/test_zero_obj.jl
Original file line number Diff line number Diff line change
@@ -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


Loading