Skip to content

Commit 121bd58

Browse files
projekterblegat
andauthored
Complex-valued variables (#121)
* First step to implement complex variables functionality * Move monomial function to correct file * Rename ordvar to ordinary_variable * Specialized complex routines for MonomialVector * fix * coefficienttype -> coefficient_type * Remove incorrect method * Add a (sometimes working) implementation for incomplete substitution * Fix old syntax * Format * Remove TODO * Add complex kind parameter to VT("name") constructor * Remove iscomplex * Remove dev dependency of MP, 0.5.3 has landed * Add ComplexKind docstring * Rename ComplexKind instances * typo * change substitution rules * Rename polycvar macro * Error for partial substitutions * Format * Complex substitution test cases * More strict substitution rules: only allow ordinary variable --------- Co-authored-by: Benoît Legat <[email protected]>
1 parent e866181 commit 121bd58

File tree

8 files changed

+220
-43
lines changed

8 files changed

+220
-43
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1313
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1414

1515
[compat]
16-
MultivariatePolynomials = "0.5"
16+
MultivariatePolynomials = "0.5.3"
1717
MutableArithmetics = "1"
1818
Reexport = "1"
1919
julia = "1"

src/DynamicPolynomials.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ function MP.similar_variable(
7272
) where {V,M,S}
7373
return MP.similar_variable(P, S)
7474
end
75-
function MP.similar_variable(
76-
::Union{PolyType{V,M},Type{<:PolyType{V,M}}},
77-
s::Symbol,
78-
) where {V,M}
79-
return Variable(string(s), V, M)
75+
function MP.similar_variable(p::PolyType{V,M}, s::Symbol) where {V,M}
76+
return Variable(string(s), V, M, isreal(p) ? REAL : COMPLEX)
77+
end
78+
function MP.similar_variable(::Type{<:PolyType{V,M}}, s::Symbol) where {V,M}
79+
return Variable(string(s), V, M, REAL) # we cannot infer this from the type
8080
end
8181

8282
include("promote.jl")

src/comp.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,19 @@ function (==)(
2727
x::Variable{<:AnyCommutative{CreationOrder}},
2828
y::Variable{<:AnyCommutative{CreationOrder}},
2929
)
30-
return x.variable_order.order.id == y.variable_order.order.id
30+
return x.variable_order.order.id == y.variable_order.order.id &&
31+
x.kind == y.kind
3132
end
3233

3334
function Base.isless(
3435
x::Variable{<:AnyCommutative{CreationOrder}},
3536
y::Variable{<:AnyCommutative{CreationOrder}},
3637
)
37-
return isless(y.variable_order.order.id, x.variable_order.order.id)
38+
if x.variable_order.order.id == y.variable_order.order.id
39+
return isless(y.kind, x.kind)
40+
else
41+
return isless(y.variable_order.order.id, x.variable_order.order.id)
42+
end
3843
end
3944

4045
# Comparison of Monomial

src/mono.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,10 @@ function __add_variables!(
113113
mono.z[map] = tmp
114114
return
115115
end
116+
117+
# for efficiency reasons
118+
function Base.conj(x::Monomial{V,M}) where {V<:Commutative,M}
119+
cv = conj.(x.vars)
120+
perm = sortperm(cv, rev = true)
121+
return Monomial{V,M}(cv[perm], x.z[perm])
122+
end

src/monomial_vector.jl

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,28 @@ end
8888
function MultivariatePolynomials.maxdegree(x::MonomialVector)
8989
return isempty(x) ? 0 : maximum(sum.(x.Z))
9090
end
91+
# Complex-valued degrees for monomial vectors
92+
for (fun, call, def, ret) in [
93+
(:extdegree_complex, :extrema, (0, 0), :((min(v1, v2), max(v1, v2)))),
94+
(:mindegree_complex, :minimum, 0, :(min(v1, v2))),
95+
(:maxdegree_complex, :maximum, 0, :(max(v1, v2))),
96+
]
97+
eval(quote
98+
function MultivariatePolynomials.$fun(x::MonomialVector)
99+
isempty(x) && return $def
100+
vars = variables(x)
101+
@assert(!any(isrealpart, vars) && !any(isimagpart, vars))
102+
grouping = isconj.(vars)
103+
v1 = $call(sum(z[grouping]) for z in x.Z)
104+
grouping = map(!, grouping)
105+
v2 = $call(sum(z[grouping]) for z in x.Z)
106+
return $ret
107+
end
108+
end)
109+
end
110+
# faster complex-related functions
111+
Base.isreal(x::MonomialVector) = all(isreal, x.vars)
112+
Base.conj(x::MonomialVector) = MonomialVector(conj.(x.vars), x.Z)
91113

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

@@ -118,7 +140,11 @@ end
118140
# TODO replace by MP function
119141
function _error_for_negative_degree(deg)
120142
if deg < 0
121-
throw(ArgumentError("The degree should be a nonnegative number but the provided degree `$deg` is negative."))
143+
throw(
144+
ArgumentError(
145+
"The degree should be a nonnegative number but the provided degree `$deg` is negative.",
146+
),
147+
)
122148
end
123149
end
124150

src/subs.jl

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,46 @@ end
99

1010
function fillmap!(
1111
vals,
12-
vars::Vector{<:Variable{<:NonCommutative}},
12+
vars::Vector{<:Variable{C}},
1313
s::MP.Substitution,
14-
)
15-
for j in eachindex(vars)
16-
if vars[j] == s.first
17-
vals[j] = s.second
14+
) where {C}
15+
# We may assign a complex or real variable to its value (ordinary substitution).
16+
# We follow the following rules:
17+
# - If a single substitution rule determines the value of a real variable, just substitute it.
18+
# - If a single substitution rule determines the value of a complex variable or its conjugate, substitute the appropriate
19+
# value whereever something related to this variable is found (i.e., the complex variable, the conjugate variable, or
20+
# its real or imaginary part)
21+
# - If a single substitution rule determines the value of the real or imaginary part of a complex variable alone, then only
22+
# replace the real or imaginary parts if they occur explicitly. Don't do a partial substitution, i.e., `z` with the rule
23+
# `zᵣ => 1` is left alone and not changed into `1 + im*zᵢ`. Even if both the real and imaginary parts are substituted as
24+
# two individual rules (which we don't know of in this method), `z` will not be replaced.
25+
if s.first.kind == REAL
26+
for j in eachindex(vars)
27+
if vars[j] == s.first
28+
vals[j] = s.second
29+
C == Commutative && break
30+
end
31+
end
32+
else
33+
s.first.kind == COMPLEX || throw(
34+
ArgumentError(
35+
"Substitution with complex variables requires the ordinary_variable in the substitution specification",
36+
),
37+
)
38+
for j in eachindex(vars)
39+
if vars[j].variable_order.order.id ==
40+
s.first.variable_order.order.id
41+
if vars[j].kind == COMPLEX
42+
vals[j] = s.second
43+
elseif vars[j].kind == CONJ
44+
vals[j] = conj(s.second)
45+
elseif vars[j].kind == REAL_PART
46+
vals[j] = real(s.second)
47+
else
48+
vals[j] = imag(s.second)
49+
end
50+
end
1851
end
19-
end
20-
end
21-
function fillmap!(
22-
vals,
23-
vars::Vector{<:Variable{<:Commutative}},
24-
s::MP.Substitution,
25-
)
26-
j = findfirst(isequal(s.first), vars)
27-
if j !== nothing
28-
vals[j] = s.second
2952
end
3053
end
3154
function fillmap!(vals, vars, s::MP.AbstractMultiSubstitution)

src/var.jl

Lines changed: 116 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
1-
export Variable, @polyvar, @ncpolyvar
1+
export Variable, @polyvar, @ncpolyvar, @complex_polyvar
22

3-
function polyarrayvar(variable_order, monomial_order, prefix, indices...)
3+
function polyarrayvar(
4+
variable_order,
5+
monomial_order,
6+
complex_kind,
7+
prefix,
8+
indices...,
9+
)
410
return map(
511
i -> Variable(
612
"$(prefix)[$(join(i, ","))]",
713
variable_order,
814
monomial_order,
15+
complex_kind,
916
),
1017
Iterators.product(indices...),
1118
)
1219
end
1320

14-
function buildpolyvar(var, variable_order, monomial_order)
21+
function buildpolyvar(var, variable_order, monomial_order, complex_kind)
1522
if isa(var, Symbol)
1623
var,
17-
:($(esc(var)) = $Variable($"$var", $variable_order, $monomial_order))
24+
:(
25+
$(esc(var)) = $Variable(
26+
$"$var",
27+
$variable_order,
28+
$monomial_order,
29+
$complex_kind,
30+
)
31+
)
1832
else
1933
isa(var, Expr) || error("Expected $var to be a variable name")
2034
Base.Meta.isexpr(var, :ref) ||
@@ -28,26 +42,28 @@ function buildpolyvar(var, variable_order, monomial_order)
2842
$(esc(varname)) = polyarrayvar(
2943
$(variable_order),
3044
$(monomial_order),
45+
$(complex_kind),
3146
$prefix,
3247
$(esc.(var.args[2:end])...),
3348
)
3449
)
3550
end
3651
end
3752

38-
function buildpolyvars(args, variable_order, monomial_order)
53+
function buildpolyvars(args, variable_order, monomial_order, complex_kind)
3954
vars = Symbol[]
4055
exprs = []
4156
for arg in args
42-
var, expr = buildpolyvar(arg, variable_order, monomial_order)
57+
var, expr =
58+
buildpolyvar(arg, variable_order, monomial_order, complex_kind)
4359
push!(vars, var)
4460
push!(exprs, expr)
4561
end
4662
return vars, exprs
4763
end
4864

4965
# Inspired from `JuMP.Containers._extract_kw_args`
50-
function _extract_kw_args(args, variable_order)
66+
function _extract_kw_args(args, variable_order, complex_kind)
5167
positional_args = Any[]
5268
monomial_order = :($(MP.Graded{MP.LexOrder}))
5369
for arg in args
@@ -56,29 +72,42 @@ function _extract_kw_args(args, variable_order)
5672
variable_order = esc(arg.args[2])
5773
elseif arg.args[1] == :monomial_order
5874
monomial_order = esc(arg.args[2])
75+
elseif arg.args[1] == :complex
76+
complex_kind = arg.args[2] == :true ? COMPLEX : REAL
5977
else
6078
error("Unrecognized keyword argument `$(arg.args[1])`")
6179
end
6280
else
6381
push!(positional_args, arg)
6482
end
6583
end
66-
return positional_args, variable_order, monomial_order
84+
return positional_args, variable_order, monomial_order, complex_kind
6785
end
6886

6987
# Variable vector x returned garanteed to be sorted so that if p is built with x then vars(p) == x
7088
macro polyvar(args...)
71-
pos_args, variable_order, monomial_order =
72-
_extract_kw_args(args, :($(Commutative{CreationOrder})))
73-
vars, exprs = buildpolyvars(pos_args, variable_order, monomial_order)
89+
pos_args, variable_order, monomial_order, complex_kind =
90+
_extract_kw_args(args, :($(Commutative{CreationOrder})), REAL)
91+
vars, exprs =
92+
buildpolyvars(pos_args, variable_order, monomial_order, complex_kind)
7493
return :($(foldl((x, y) -> :($x; $y), exprs, init = :()));
7594
$(Expr(:tuple, esc.(vars)...)))
7695
end
7796

7897
macro ncpolyvar(args...)
79-
pos_args, variable_order, monomial_order =
80-
_extract_kw_args(args, :($(NonCommutative{CreationOrder})))
81-
vars, exprs = buildpolyvars(pos_args, variable_order, monomial_order)
98+
pos_args, variable_order, monomial_order, complex_kind =
99+
_extract_kw_args(args, :($(NonCommutative{CreationOrder})), REAL)
100+
vars, exprs =
101+
buildpolyvars(pos_args, variable_order, monomial_order, complex_kind)
102+
return :($(foldl((x, y) -> :($x; $y), exprs, init = :()));
103+
$(Expr(:tuple, esc.(vars)...)))
104+
end
105+
106+
macro complex_polyvar(args...)
107+
pos_args, variable_order, monomial_order, complex_kind =
108+
_extract_kw_args(args, :($(Commutative{CreationOrder})), COMPLEX)
109+
vars, exprs =
110+
buildpolyvars(pos_args, variable_order, monomial_order, complex_kind)
82111
return :($(foldl((x, y) -> :($x; $y), exprs, init = :()));
83112
$(Expr(:tuple, esc.(vars)...)))
84113
end
@@ -114,19 +143,54 @@ function instantiate(::Type{NonCommutative{O}}) where {O}
114143
return NonCommutative(instantiate(O))
115144
end
116145

146+
"""
147+
ComplexKind
148+
149+
The type used to identify whether a variable is complex-valued. It has the following instances:
150+
- `REAL`: the variable is real-valued, [`conj`](@ref) and [`real`](@ref) are identities, [`imag`](@ref) is zero.
151+
- `COMPLEX`: the variable is a declared complex-valued variable with distinct conjugate, real, and imaginary part.
152+
[`ordinary_variable`](@ref) is an identity.
153+
- `CONJ`: the variable is the conjugate of a declared complex-valued variable. [`ordinary_variable`](@ref) is equal to
154+
[`conj`](@ref). It will print with an over-bar.
155+
- `REAL_PART`: the variable is the real part of a complex-valued variable. It is real-valued itself: [`conj`](@ref) and
156+
[`real`](@ref) are identities, [`imag`](@ref) is zero. [`ordinary_variable`](@ref) will give back the original complex-valued
157+
_declared_ variable from which the real part was extracted. It will print with an `ᵣ` subscript.
158+
- `IMAG_PART`: the variable is the imaginary part of a declared complex-valued variable (or the negative imaginary part of the
159+
conjugate of a declared complex-valued variable). It is real-valued itself: [`conj`](@ref) and [`real`](@ref) are identities,
160+
[`imag`](@ref) is zero. [`ordinary_variable`](@ref) will give back the original complex-valued _declared_ variable from which
161+
the imaginary part was extracted. It will print with an `ᵢ` subscript.
162+
"""
163+
@enum ComplexKind REAL COMPLEX CONJ REAL_PART IMAG_PART
164+
117165
struct Variable{V,M} <: AbstractVariable
118166
name::String
167+
kind::ComplexKind
119168
variable_order::V
120169

121-
function Variable{V,M}(name::AbstractString) where {V<:AbstractVariableOrdering,M<:MP.AbstractMonomialOrdering}
122-
return new{V,M}(convert(String, name), instantiate(V))
170+
function Variable{V,M}(
171+
name::AbstractString,
172+
kind::ComplexKind = REAL,
173+
) where {V<:AbstractVariableOrdering,M<:MP.AbstractMonomialOrdering}
174+
return new{V,M}(convert(String, name), kind, instantiate(V))
123175
end
176+
124177
function Variable(
125178
name::AbstractString,
126179
::Type{V},
127180
::Type{M},
181+
kind::ComplexKind = REAL,
128182
) where {V<:AbstractVariableOrdering,M<:MP.AbstractMonomialOrdering}
129-
return new{V,M}(convert(String, name), instantiate(V))
183+
return new{V,M}(convert(String, name), kind, instantiate(V))
184+
end
185+
186+
function Variable(
187+
from::Variable{V,M},
188+
kind::ComplexKind,
189+
) where {V<:AbstractVariableOrdering,M<:MP.AbstractMonomialOrdering}
190+
(from.kind == REAL && kind == REAL) ||
191+
(from.kind != REAL && kind != REAL) ||
192+
error("Cannot change the complex type of an existing variable")
193+
return new{V,M}(from.name, kind, from.variable_order)
130194
end
131195
end
132196

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

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

217+
Base.isreal(x::Variable{C}) where {C} = x.kind != COMPLEX && x.kind != CONJ
218+
MP.isrealpart(x::Variable{C}) where {C} = x.kind == REAL_PART
219+
MP.isimagpart(x::Variable{C}) where {C} = x.kind == IMAG_PART
220+
MP.isconj(x::Variable{C}) where {C} = x.kind == CONJ
221+
function MP.ordinary_variable(x::Variable)
222+
return x.kind == REAL || x.kind == COMPLEX ? x : Variable(x, COMPLEX)
223+
end
224+
225+
function Base.conj(x::Variable)
226+
if x.kind == COMPLEX
227+
return Variable(x, CONJ)
228+
elseif x.kind == CONJ
229+
return Variable(x, COMPLEX)
230+
else
231+
return x
232+
end
233+
end
234+
235+
function Base.real(x::Variable)
236+
if x.kind == COMPLEX || x.kind == CONJ
237+
return Variable(x, REAL_PART)
238+
else
239+
return x
240+
end
241+
end
242+
function Base.imag(x::Variable{V,M}) where {V,M}
243+
if x.kind == COMPLEX
244+
return Variable(x, IMAG_PART)
245+
elseif x.kind == CONJ
246+
return _Term{V,M,Int}(-1, Monomial(Variable(x, IMAG_PART)))
247+
else
248+
return MA.Zero()
249+
end
250+
end
251+
153252
function mergevars_to!(
154253
vars::Vector{PV},
155254
varsvec::Vector{Vector{PV}},

0 commit comments

Comments
 (0)