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 all 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
53 changes: 38 additions & 15 deletions src/subs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,46 @@ 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
s.first.kind == COMPLEX || throw(
ArgumentError(
"Substitution with complex variables requires the ordinary_variable in the substitution specification",
),
)
for j in eachindex(vars)
if vars[j].variable_order.order.id ==
s.first.variable_order.order.id
if vars[j].kind == COMPLEX
vals[j] = s.second
elseif vars[j].kind == CONJ
vals[j] = conj(s.second)
elseif vars[j].kind == REAL_PART
vals[j] = real(s.second)
else
vals[j] = imag(s.second)
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
133 changes: 116 additions & 17 deletions src/var.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
export Variable, @polyvar, @ncpolyvar
export Variable, @polyvar, @ncpolyvar, @complex_polyvar

function polyarrayvar(variable_order, monomial_order, prefix, indices...)
function polyarrayvar(
variable_order,
monomial_order,
complex_kind,
prefix,
indices...,
)
return map(
i -> Variable(
"$(prefix)[$(join(i, ","))]",
variable_order,
monomial_order,
complex_kind,
),
Iterators.product(indices...),
)
end

function buildpolyvar(var, variable_order, monomial_order)
function buildpolyvar(var, variable_order, monomial_order, complex_kind)
if isa(var, Symbol)
var,
:($(esc(var)) = $Variable($"$var", $variable_order, $monomial_order))
:(
$(esc(var)) = $Variable(
$"$var",
$variable_order,
$monomial_order,
$complex_kind,
)
)
else
isa(var, Expr) || error("Expected $var to be a variable name")
Base.Meta.isexpr(var, :ref) ||
Expand All @@ -28,26 +42,28 @@ function buildpolyvar(var, variable_order, monomial_order)
$(esc(varname)) = polyarrayvar(
$(variable_order),
$(monomial_order),
$(complex_kind),
$prefix,
$(esc.(var.args[2:end])...),
)
)
end
end

function buildpolyvars(args, variable_order, monomial_order)
function buildpolyvars(args, variable_order, monomial_order, complex_kind)
vars = Symbol[]
exprs = []
for arg in args
var, expr = buildpolyvar(arg, variable_order, monomial_order)
var, expr =
buildpolyvar(arg, variable_order, monomial_order, complex_kind)
push!(vars, var)
push!(exprs, expr)
end
return vars, exprs
end

# Inspired from `JuMP.Containers._extract_kw_args`
function _extract_kw_args(args, variable_order)
function _extract_kw_args(args, variable_order, complex_kind)
positional_args = Any[]
monomial_order = :($(MP.Graded{MP.LexOrder}))
for arg in args
Expand All @@ -56,29 +72,42 @@ function _extract_kw_args(args, variable_order)
variable_order = esc(arg.args[2])
elseif arg.args[1] == :monomial_order
monomial_order = esc(arg.args[2])
elseif arg.args[1] == :complex
complex_kind = arg.args[2] == :true ? COMPLEX : REAL
else
error("Unrecognized keyword argument `$(arg.args[1])`")
end
else
push!(positional_args, arg)
end
end
return positional_args, variable_order, monomial_order
return positional_args, variable_order, monomial_order, complex_kind
end

# Variable vector x returned garanteed to be sorted so that if p is built with x then vars(p) == x
macro polyvar(args...)
pos_args, variable_order, monomial_order =
_extract_kw_args(args, :($(Commutative{CreationOrder})))
vars, exprs = buildpolyvars(pos_args, variable_order, monomial_order)
pos_args, variable_order, monomial_order, complex_kind =
_extract_kw_args(args, :($(Commutative{CreationOrder})), REAL)
vars, exprs =
buildpolyvars(pos_args, variable_order, monomial_order, complex_kind)
return :($(foldl((x, y) -> :($x; $y), exprs, init = :()));
$(Expr(:tuple, esc.(vars)...)))
end

macro ncpolyvar(args...)
pos_args, variable_order, monomial_order =
_extract_kw_args(args, :($(NonCommutative{CreationOrder})))
vars, exprs = buildpolyvars(pos_args, variable_order, monomial_order)
pos_args, variable_order, monomial_order, complex_kind =
_extract_kw_args(args, :($(NonCommutative{CreationOrder})), REAL)
vars, exprs =
buildpolyvars(pos_args, variable_order, monomial_order, complex_kind)
return :($(foldl((x, y) -> :($x; $y), exprs, init = :()));
$(Expr(:tuple, esc.(vars)...)))
end

macro complex_polyvar(args...)
pos_args, variable_order, monomial_order, complex_kind =
_extract_kw_args(args, :($(Commutative{CreationOrder})), COMPLEX)
vars, exprs =
buildpolyvars(pos_args, variable_order, monomial_order, complex_kind)
return :($(foldl((x, y) -> :($x; $y), exprs, init = :()));
$(Expr(:tuple, esc.(vars)...)))
end
Expand Down Expand Up @@ -114,19 +143,54 @@ function instantiate(::Type{NonCommutative{O}}) where {O}
return NonCommutative(instantiate(O))
end

"""
ComplexKind

The type used to identify whether a variable is complex-valued. It has the following instances:
- `REAL`: the variable is real-valued, [`conj`](@ref) and [`real`](@ref) are identities, [`imag`](@ref) is zero.
- `COMPLEX`: the variable is a declared complex-valued variable with distinct conjugate, real, and imaginary part.
[`ordinary_variable`](@ref) is an identity.
- `CONJ`: the variable is the conjugate of a declared complex-valued variable. [`ordinary_variable`](@ref) is equal to
[`conj`](@ref). It will print with an over-bar.
- `REAL_PART`: the variable is the real part of a complex-valued variable. It is real-valued itself: [`conj`](@ref) and
[`real`](@ref) are identities, [`imag`](@ref) is zero. [`ordinary_variable`](@ref) will give back the original complex-valued
_declared_ variable from which the real part was extracted. It will print with an `ᵣ` subscript.
- `IMAG_PART`: the variable is the imaginary part of a declared complex-valued variable (or the negative imaginary part of the
conjugate of a declared complex-valued variable). It is real-valued itself: [`conj`](@ref) and [`real`](@ref) are identities,
[`imag`](@ref) is zero. [`ordinary_variable`](@ref) will give back the original complex-valued _declared_ variable from which
the imaginary part was extracted. It will print with an `ᵢ` subscript.
"""
@enum ComplexKind REAL COMPLEX CONJ REAL_PART IMAG_PART

struct Variable{V,M} <: AbstractVariable
name::String
kind::ComplexKind
variable_order::V

function Variable{V,M}(name::AbstractString) where {V<:AbstractVariableOrdering,M<:MP.AbstractMonomialOrdering}
return new{V,M}(convert(String, name), instantiate(V))
function Variable{V,M}(
name::AbstractString,
kind::ComplexKind = REAL,
) where {V<:AbstractVariableOrdering,M<:MP.AbstractMonomialOrdering}
return new{V,M}(convert(String, name), kind, instantiate(V))
end

function Variable(
name::AbstractString,
::Type{V},
::Type{M},
kind::ComplexKind = REAL,
) where {V<:AbstractVariableOrdering,M<:MP.AbstractMonomialOrdering}
return new{V,M}(convert(String, name), instantiate(V))
return new{V,M}(convert(String, name), kind, instantiate(V))
end

function Variable(
from::Variable{V,M},
kind::ComplexKind,
) where {V<:AbstractVariableOrdering,M<:MP.AbstractMonomialOrdering}
(from.kind == REAL && kind == REAL) ||
(from.kind != REAL && kind != REAL) ||
error("Cannot change the complex type of an existing variable")
return new{V,M}(from.name, kind, from.variable_order)
end
end

Expand All @@ -150,6 +214,41 @@ MP.ordering(::Type{Variable{V,M}}) where {V,M} = M

iscomm(::Type{Variable{C}}) where {C} = C

Base.isreal(x::Variable{C}) where {C} = x.kind != COMPLEX && x.kind != CONJ
MP.isrealpart(x::Variable{C}) where {C} = x.kind == REAL_PART
MP.isimagpart(x::Variable{C}) where {C} = x.kind == IMAG_PART
MP.isconj(x::Variable{C}) where {C} = x.kind == CONJ
function MP.ordinary_variable(x::Variable)
return x.kind == REAL || x.kind == COMPLEX ? x : Variable(x, COMPLEX)
end

function Base.conj(x::Variable)
if x.kind == COMPLEX
return Variable(x, CONJ)
elseif x.kind == CONJ
return Variable(x, COMPLEX)
else
return x
end
end

function Base.real(x::Variable)
if x.kind == COMPLEX || x.kind == CONJ
return Variable(x, REAL_PART)
else
return x
end
end
function Base.imag(x::Variable{V,M}) where {V,M}
if x.kind == COMPLEX
return Variable(x, IMAG_PART)
elseif x.kind == CONJ
return _Term{V,M,Int}(-1, Monomial(Variable(x, IMAG_PART)))
else
return MA.Zero()
end
end

function mergevars_to!(
vars::Vector{PV},
varsvec::Vector{Vector{PV}},
Expand Down
Loading
Loading