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

Complex-valued variables #121

Merged
merged 29 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b46b905
First step to implement complex variables functionality
projekter Dec 26, 2022
a5cb35d
Move monomial function to correct file
projekter Dec 26, 2022
91583e2
Rename ordvar to ordinary_variable
projekter Apr 21, 2023
b3d8d73
Specialized complex routines for MonomialVector
projekter May 9, 2023
0cd4cfd
fix
projekter May 9, 2023
098ee29
coefficienttype -> coefficient_type
blegat May 10, 2023
43a0a61
Remove incorrect method
blegat May 10, 2023
b93b6bc
Merge branch 'bl/coefficient_type' of https://github.com/JuliaAlgebra…
projekter May 22, 2023
e417490
Merge branch 'master' of https://github.com/JuliaAlgebra/DynamicPolyn…
projekter May 28, 2023
214d169
Add a (sometimes working) implementation for incomplete substitution
projekter May 28, 2023
c5c1c99
Fix old syntax
projekter May 31, 2023
25ac2cf
Merge branch 'master' of https://github.com/JuliaAlgebra/DynamicPolyn…
projekter Jul 7, 2023
31a8d4c
Format
projekter Jul 7, 2023
a19c0b0
Remove TODO
projekter Aug 17, 2023
0bf8175
Merge branch 'master' of https://github.com/JuliaAlgebra/DynamicPolyn…
projekter Aug 24, 2023
6b055bd
Add complex kind parameter to VT("name") constructor
projekter Aug 24, 2023
a61aaa2
Remove iscomplex
projekter Sep 1, 2023
f87fb62
Remove dev dependency of MP, 0.5.3 has landed
projekter Nov 30, 2023
71ffcb5
Merge branch 'master' of https://github.com/JuliaAlgebra/DynamicPolyn…
projekter Nov 30, 2023
f9873bf
Merge remote-tracking branch 'upstream/master'
projekter Feb 19, 2024
da7fd62
Add ComplexKind docstring
projekter Feb 21, 2024
f8d3214
Rename ComplexKind instances
projekter Feb 21, 2024
7e07585
typo
projekter May 4, 2024
8cf9c12
change substitution rules
projekter May 4, 2024
6c230f6
Rename polycvar macro
projekter May 4, 2024
a6e1d80
Error for partial substitutions
projekter May 8, 2024
f9337ff
Format
projekter May 8, 2024
e0ad44d
Complex substitution test cases
projekter May 8, 2024
0845707
More strict substitution rules: only allow ordinary variable
projekter May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
MultivariatePolynomials = "0.5"
MultivariatePolynomials = "0.5.3"
MutableArithmetics = "1"
Reexport = "1"
julia = "1"
Expand Down
10 changes: 5 additions & 5 deletions src/DynamicPolynomials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ function MP.similar_variable(
) where {V,M,S}
return MP.similar_variable(P, S)
end
function MP.similar_variable(
::Union{PolyType{V,M},Type{<:PolyType{V,M}}},
s::Symbol,
) where {V,M}
return Variable(string(s), V, M)
function MP.similar_variable(p::PolyType{V,M}, s::Symbol) where {V,M}
return Variable(string(s), V, M, isreal(p) ? REAL : COMPLEX)
end
function MP.similar_variable(::Type{<:PolyType{V,M}}, s::Symbol) where {V,M}
return Variable(string(s), V, M, REAL) # we cannot infer this from the type
end

include("promote.jl")
Expand Down
9 changes: 7 additions & 2 deletions src/comp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,19 @@ function (==)(
x::Variable{<:AnyCommutative{CreationOrder}},
y::Variable{<:AnyCommutative{CreationOrder}},
)
return x.variable_order.order.id == y.variable_order.order.id
return x.variable_order.order.id == y.variable_order.order.id &&
x.kind == y.kind
end

function Base.isless(
x::Variable{<:AnyCommutative{CreationOrder}},
y::Variable{<:AnyCommutative{CreationOrder}},
)
return isless(y.variable_order.order.id, x.variable_order.order.id)
if x.variable_order.order.id == y.variable_order.order.id
return isless(y.kind, x.kind)
else
return isless(y.variable_order.order.id, x.variable_order.order.id)
end
end

# Comparison of Monomial
Expand Down
7 changes: 7 additions & 0 deletions src/mono.jl
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,10 @@ function __add_variables!(
mono.z[map] = tmp
return
end

# for efficiency reasons
function Base.conj(x::Monomial{V,M}) where {V<:Commutative,M}
cv = conj.(x.vars)
perm = sortperm(cv, rev = true)
return Monomial{V,M}(cv[perm], x.z[perm])
end
28 changes: 27 additions & 1 deletion src/monomial_vector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,28 @@ end
function MultivariatePolynomials.maxdegree(x::MonomialVector)
return isempty(x) ? 0 : maximum(sum.(x.Z))
end
# Complex-valued degrees for monomial vectors
for (fun, call, def, ret) in [
(:extdegree_complex, :extrema, (0, 0), :((min(v1, v2), max(v1, v2)))),
(:mindegree_complex, :minimum, 0, :(min(v1, v2))),
(:maxdegree_complex, :maximum, 0, :(max(v1, v2))),
]
eval(quote
function MultivariatePolynomials.$fun(x::MonomialVector)
isempty(x) && return $def
vars = variables(x)
@assert(!any(isrealpart, vars) && !any(isimagpart, vars))
grouping = isconj.(vars)
v1 = $call(sum(z[grouping]) for z in x.Z)
grouping = map(!, grouping)
v2 = $call(sum(z[grouping]) for z in x.Z)
return $ret
end
end)
end
# faster complex-related functions
Base.isreal(x::MonomialVector) = all(isreal, x.vars)
Base.conj(x::MonomialVector) = MonomialVector(conj.(x.vars), x.Z)

MP.variables(m::Union{Monomial,MonomialVector}) = m.vars

Expand Down Expand Up @@ -121,7 +143,11 @@ end
# TODO replace by MP function
function _error_for_negative_degree(deg)
if deg < 0
throw(ArgumentError("The degree should be a nonnegative number but the provided degree `$deg` is negative."))
throw(
ArgumentError(
"The degree should be a nonnegative number but the provided degree `$deg` is negative.",
),
)
end
end

Expand Down
76 changes: 61 additions & 15 deletions src/subs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,69 @@ end

function fillmap!(
vals,
vars::Vector{<:Variable{<:NonCommutative}},
vars::Vector{<:Variable{C}},
s::MP.Substitution,
)
for j in eachindex(vars)
if vars[j] == s.first
vals[j] = s.second
) where {C}
# We may assign a complex or real variable to its value (ordinary substitution).
# We follow the following rules:
# - If a single substitution rule determines the value of a real variable, just substitute it.
# - If a single substitution rule determines the value of a complex variable or its conjugate, substitute the appropriate
# value whereever something related to this variable is found (i.e., the complex variable, the conjugate variable, or
# its real or imaginary part)
# - If a single substitution rule determines the value of the real or imaginary part of a complex variable alone, then only
# replace the real or imaginary parts if they occur explicitly. Don't do a partial substitution, i.e., `z` with the rule
# `zᵣ => 1` is left alone and not changed into `1 + im*zᵢ`. Even if both the real and imaginary parts are substituted as
# two individual rules (which we don't know of in this method), `z` will not be replaced.
if s.first.kind == REAL
for j in eachindex(vars)
if vars[j] == s.first
vals[j] = s.second
C == Commutative && break
end
end
else
for j in eachindex(vars)
if vars[j].variable_order.order.id ==
s.first.variable_order.order.id
if s.first.kind == COMPLEX || s.first.kind == CONJ
value = s.first.kind == CONJ ? conj(s.second) : s.second
if vars[j].kind == COMPLEX
vals[j] = value
elseif vars[j].kind == CONJ
vals[j] = conj(value)
elseif vars[j].kind == REAL_PART
vals[j] = real(value)
else
vals[j] = imag(value)
end
elseif s.first.kind == REAL_PART
isreal(s.second) || error(
"Cannot assign a complex value to the real part of an expression",
)
value = real(s.second) # just to make sure the type is correct
if vars[j].kind == REAL_PART
vals[j] = value
elseif vars[j].kind != IMAG_PART
error(
"Found complex variable with substitution of real part - not implemented",
)
end
else
@assert(s.first.kind == IMAG_PART)
isreal(s.second) || error(
"Cannot assign a complex value to the imaginary part of an expression",
)
value = real(s.second) # just to make sure the type is correct
if vars[j].kind == IMAG_PART
vals[j] = value
elseif vars[j].kind != REAL_PART
error(
"Found complex variable with substitution of imaginary part - not implemented",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, could you add some tests for these edge cases ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a few testcases. Note that I did not use the substitution syntax without explicit variable specification. This is because variables(p) would list all variables, complex, conjugates, real, and imaginary parts separately. So you'd have to specify values for all of them (unnecessarily - and this would even be more restrictive as then: z + real(z), called with arguments (1+2im, 1) would trigger the new error, because z occurs but a value is assigned to real(z)). Either the whole variable assignment would need to be redesigned or we just say that for complex variables, you should always explicitly specify a substitution rule.
(But even then, substitution rules could be redundant; [z, real(z)] => [1+2im, 1] is also a valid rule. Should we check whether the rules are self-consistent? Given that variables occuring twice are also not checked, I think the current lax behavior is fine.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, that's an issue. We should only allow COMPLEX_PART to used in substitutions then. In this PR, I think we should target the simplest implementation that's bullet proof. We can try to make something more complicated but I'd rather do it in a separate PR if there is a use case for it. So I'd just restrict z to be a COMPLEX_PART whenever z => ... is used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This simplifies the substitution process significantly. I don't know whether I like it, but there's some sense to it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can revisit in a follow up PR without being breaking. We'll be able to recover your implementation easily from this branch.

)
end
end
end
end
end
end
function fillmap!(
vals,
vars::Vector{<:Variable{<:Commutative}},
s::MP.Substitution,
)
j = findfirst(isequal(s.first), vars)
if j !== nothing
vals[j] = s.second
end
end
function fillmap!(vals, vars, s::MP.AbstractMultiSubstitution)
Expand Down
Loading