-
Notifications
You must be signed in to change notification settings - Fork 11
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
Add qexpand functionality and cleanup scalings #59
Changes from 4 commits
38b3dc8
41a4aad
5e9e0ec
393a6fa
7e1e003
0d66026
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
[default.extend-words] | ||
ket = "ket" | ||
ket = "ket" | ||
BA = "BA" |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,8 +81,8 @@ | |
_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(terms) : SAdd{S}(d,Set(terms),terms) | ||
end | ||
isexpr(::SAdd) = true | ||
iscall(::SAdd) = true | ||
|
@@ -99,6 +99,11 @@ | |
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)]) | ||
|
@@ -109,11 +114,6 @@ | |
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 | ||
|
||
|
@@ -128,7 +128,7 @@ | |
terms | ||
function SMulOperator(terms) | ||
coeff, cleanterms = prefactorscalings(terms) | ||
coeff*new(cleanterms) | ||
coeff*new(flattenop(*,cleanterms)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure this is the best order of operations. You might have A second request: I know this state is kinda inherited already, but now that we are doing more and more fancy processing on construction, I want us to be a bit more disciplined about what goes in the constructor and what goes in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see any reason why this would be a bad idea. My original thought process is that using inner constructor methods would be more idiomatic, but perhaps you're right in that it is too fancy for our purposes. It would also make the source code more clear to future developers if we do the processing in the calls to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! Please proceed with that plan. For more context: one thing that is pestering the back of my mind is how fancy constructors plagued the development of SymPy. At some point, for various reasons related to more advanced simplification rules, it became useful to be able to construct the most raw unsimplified versions of expressions, but all the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that's interesting. Just out of curiosity, would there be any performance benefits in simplifying the constructors and moving the processing to conversion functions? I can't find any discussion about this in Julia discourse or on GitHub issue pages. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think there would be anything (besides potentially avoiding situation where we happen to write repetitive canonicalization steps). Nothing inherent to the language though (constructors and other functions are treated the same way by the compiler, which is also why we are permitted to return object of type A from the constructor of type B (strictly speaking we are not really constructing type B anymore)) However, this separation of concerns we are discussing above might in some situation make the functions more "type stable" which permits the compiler to make a bunch of optimizations (mostly removing runtime type checks and allocations). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, this is good to know. |
||
end | ||
end | ||
isexpr(::SMulOperator) = true | ||
|
@@ -155,22 +155,22 @@ | |
julia> @op A; @op B; | ||
|
||
julia> A ⊗ B | ||
A⊗B | ||
(A⊗B) | ||
``` | ||
""" | ||
@withmetadata struct STensor{T<:QObj} <: Symbolic{T} | ||
terms | ||
function STensor{S}(terms) where S | ||
coeff, cleanterms = prefactorscalings(terms) | ||
coeff * new{S}(cleanterms) | ||
coeff * new{S}(flattenop(⊗,cleanterms)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question about order of operations and moving this to |
||
end | ||
end | ||
isexpr(::STensor) = true | ||
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}() | ||
|
@@ -182,9 +182,9 @@ | |
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 | ||
|
||
|
@@ -202,7 +202,7 @@ | |
op1 | ||
op2 | ||
function SCommutator(o1, o2) | ||
coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) | ||
coeff, cleanterms = prefactorscalings([o1 o2]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question about dropping the custom constructor and moving this to |
||
cleanterms[1] === cleanterms[2] ? SZeroOperator() : coeff*new(cleanterms...) | ||
end | ||
end | ||
|
@@ -218,7 +218,6 @@ | |
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 | ||
|
||
"""Symbolic anticommutator of two operators | ||
|
||
|
@@ -233,7 +232,7 @@ | |
op1 | ||
op2 | ||
function SAnticommutator(o1, o2) | ||
coeff, cleanterms = prefactorscalings([o1 o2], scalar=true) | ||
coeff, cleanterms = prefactorscalings([o1 o2]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same question about dropping the custom constructor and moving this to |
||
coeff*new(cleanterms...) | ||
end | ||
end | ||
|
@@ -249,4 +248,3 @@ | |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
## | ||
# 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 | ||
|
||
"""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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,4 +92,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{Number}, Symbolic{Complex}}) = isa(x, SZero) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is piracy. Why is this needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My bad about the piracy. It was intended for when we process |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks a bit suspicious. There is no one-to-one correspondence between the dictionary, set, and list forms anymore. I think what you want is actually
countmap_flatten
as seen hereQuantumSymbolics.jl/src/QSymbolicsBase/basic_ops_homogeneous.jl
Line 97 in 079ea5e
Given that there is already flattening done in the
+
operation, I think we can just not do any flattening here. Just take the dictionary and do not worry about processing it. Any normal use ofSAdd
, usually through the use of+
will already take care of flattening. This is a fairly general statement: fancy processing should be done in calls to+
and*
, not in constructors.Also, something I did not notice previously, the variable names are misleading here. It should be
(obj, coef) in d
not(coef, obj) in d
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this particular case, unlike the other constructors, we probably still want to keep the inner constructor that makes sure to create the set and vector.