@@ -15,44 +15,104 @@ function contains_variable(x, wrt)
15
15
any (y -> occursin (y, x), wrt)
16
16
end
17
17
18
+ """
19
+ Possible reasons why a term is not polynomial
20
+ """
21
+ MTK. EnumX. @enumx NonPolynomialReason begin
22
+ NonIntegerExponent
23
+ ExponentContainsUnknowns
24
+ BaseNotPolynomial
25
+ UnrecognizedOperation
26
+ end
27
+
28
+ function display_reason (reason:: NonPolynomialReason.T , sym)
29
+ if reason == NonPolynomialReason. NonIntegerExponent
30
+ pow = arguments (sym)[2 ]
31
+ " In $sym : Exponent $pow is not an integer"
32
+ elseif reason == NonPolynomialReason. ExponentContainsUnknowns
33
+ pow = arguments (sym)[2 ]
34
+ " In $sym : Exponent $pow contains unknowns of the system"
35
+ elseif reason == NonPolynomialReason. BaseNotPolynomial
36
+ base = arguments (sym)[1 ]
37
+ " In $sym : Base $base is not a polynomial in the unknowns"
38
+ elseif reason == NonPolynomialReason. UnrecognizedOperation
39
+ op = operation (sym)
40
+ """
41
+ In $sym : Operation $op is not recognized. Allowed polynomial operations are \
42
+ `*, /, +, -, ^`.
43
+ """
44
+ else
45
+ error (" This should never happen. Please open an issue in ModelingToolkit.jl." )
46
+ end
47
+ end
48
+
49
+ mutable struct PolynomialData
50
+ non_polynomial_terms:: Vector{BasicSymbolic}
51
+ reasons:: Vector{NonPolynomialReason.T}
52
+ has_parametric_exponent:: Bool
53
+ end
54
+
55
+ PolynomialData () = PolynomialData (BasicSymbolic[], NonPolynomialReason. T[], false )
56
+
57
+ struct NotPolynomialError <: Exception
58
+ eq:: Equation
59
+ data:: PolynomialData
60
+ end
61
+
62
+ function Base. showerror (io:: IO , err:: NotPolynomialError )
63
+ println (io,
64
+ " Equation $(err. eq) is not a polynomial in the unknowns for the following reasons:" )
65
+ for (term, reason) in zip (err. data. non_polynomial_terms, err. data. reasons)
66
+ println (io, display_reason (reason, term))
67
+ end
68
+ end
69
+
70
+ function is_polynomial! (data, y, wrt)
71
+ process_polynomial! (data, y, wrt)
72
+ isempty (data. reasons)
73
+ end
74
+
18
75
"""
19
76
$(TYPEDSIGNATURES)
20
77
21
- Check if `x` is polynomial with respect to the variables in `wrt`.
78
+ Return information about the polynmial `x` with respect to variables in `wrt`,
79
+ writing said information to `data`.
22
80
"""
23
- function is_polynomial ( x, wrt)
81
+ function process_polynomial! (data :: PolynomialData , x, wrt)
24
82
x = unwrap (x)
25
83
symbolic_type (x) == NotSymbolic () && return true
26
84
iscall (x) || return true
27
85
contains_variable (x, wrt) || return true
28
86
any (isequal (x), wrt) && return true
29
87
30
88
if operation (x) in (* , + , - , / )
31
- return all (y -> is_polynomial ( y, wrt), arguments (x))
89
+ return all (y -> is_polynomial! (data, y, wrt), arguments (x))
32
90
end
33
91
if operation (x) == (^ )
34
92
b, p = arguments (x)
93
+ is_pow_integer = symtype (p) <: Integer
94
+ if ! is_pow_integer
95
+ push! (data. non_polynomial_terms, x)
96
+ push! (data. reasons, NonPolynomialReason. NonIntegerExponent)
97
+ end
35
98
if symbolic_type (p) != NotSymbolic ()
36
- @warn " In $x : Exponent $p cannot be symbolic"
37
- is_pow_integer = false
38
- elseif ! (p isa Integer)
39
- @warn " In $x : Exponent $p is not an integer"
40
- is_pow_integer = false
41
- else
42
- is_pow_integer = true
99
+ data. has_parametric_exponent = true
43
100
end
44
101
45
102
exponent_has_unknowns = contains_variable (p, wrt)
46
103
if exponent_has_unknowns
47
- @warn " In $x : Exponent $p cannot contain unknowns of the system."
104
+ push! (data. non_polynomial_terms, x)
105
+ push! (data. reasons, NonPolynomialReason. ExponentContainsUnknowns)
48
106
end
49
- base_polynomial = is_polynomial ( b, wrt)
107
+ base_polynomial = is_polynomial! (data, b, wrt)
50
108
if ! base_polynomial
51
- @warn " In $x : Base is not a polynomial"
109
+ push! (data. non_polynomial_terms, x)
110
+ push! (data. reasons, NonPolynomialReason. BaseNotPolynomial)
52
111
end
53
112
return base_polynomial && ! exponent_has_unknowns && is_pow_integer
54
113
end
55
- @warn " In $x : Unrecognized operation $(operation (x)) . Allowed polynomial operations are `*, +, -, ^`"
114
+ push! (data. non_polynomial_terms, x)
115
+ push! (data. reasons, NonPolynomialReason. UnrecognizedOperation)
56
116
return false
57
117
end
58
118
@@ -182,11 +242,17 @@ The problem will be solved by HomotopyContinuation.jl. The resultant `NonlinearS
182
242
will contain the polynomial root closest to the point specified by `u0map` (if real roots
183
243
exist for the system).
184
244
185
- Keyword arguments are forwarded to `HomotopyContinuation.solver_startsystems`.
245
+ Keyword arguments:
246
+ - `eval_expression`: Whether to `eval` the generated functions or use a `RuntimeGeneratedFunction`.
247
+ - `eval_module`: The module to use for `eval`/`@RuntimeGeneratedFunction`
248
+ - `warn_parametric_exponent`: Whether to warn if the system contains a parametric
249
+ exponent preventing the homotopy from being cached.
250
+
251
+ All other keyword arguments are forwarded to `HomotopyContinuation.solver_startsystems`.
186
252
"""
187
253
function MTK. HomotopyContinuationProblem (
188
254
sys:: NonlinearSystem , u0map, parammap = nothing ; eval_expression = false ,
189
- eval_module = ModelingToolkit, kwargs... )
255
+ eval_module = ModelingToolkit, warn_parametric_exponent = true , kwargs... )
190
256
if ! iscomplete (sys)
191
257
error (" A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`" )
192
258
end
@@ -200,9 +266,14 @@ function MTK.HomotopyContinuationProblem(
200
266
eqs = full_equations (sys)
201
267
202
268
denoms = []
269
+ has_parametric_exponents = false
203
270
eqs2 = map (eqs) do eq
204
- if ! is_polynomial (eq. lhs, dvs) || ! is_polynomial (eq. rhs, dvs)
205
- error (" Equation $eq is not a polynomial in the unknowns. See warnings for further details." )
271
+ data = PolynomialData ()
272
+ process_polynomial! (data, eq. lhs, dvs)
273
+ process_polynomial! (data, eq. rhs, dvs)
274
+ has_parametric_exponents |= data. has_parametric_exponent
275
+ if ! isempty (data. non_polynomial_terms)
276
+ throw (NotPolynomialError (eq, data))
206
277
end
207
278
num, den = handle_rational_polynomials (eq. rhs - eq. lhs, dvs)
208
279
@@ -235,7 +306,18 @@ function MTK.HomotopyContinuationProblem(
235
306
236
307
obsfn = MTK. ObservedFunctionCache (sys; eval_expression, eval_module)
237
308
238
- solver_and_starts = HomotopyContinuation. solver_startsolutions (mtkhsys; kwargs... )
309
+ if has_parametric_exponents
310
+ if warn_parametric_exponent
311
+ @warn """
312
+ The system has parametric exponents, preventing caching of the homotopy. \
313
+ This will cause `solve` to be slower. Pass `warn_parametric_exponent \
314
+ = false` to turn off this warning
315
+ """
316
+ end
317
+ solver_and_starts = nothing
318
+ else
319
+ solver_and_starts = HomotopyContinuation. solver_startsolutions (mtkhsys; kwargs... )
320
+ end
239
321
return MTK. HomotopyContinuationProblem (
240
322
u0, mtkhsys, denominator, sys, obsfn, solver_and_starts)
241
323
end
@@ -260,8 +342,13 @@ Extra keyword arguments:
260
342
"""
261
343
function CommonSolve. solve (prob:: MTK.HomotopyContinuationProblem ,
262
344
alg = nothing ; show_progress = false , denominator_abstol = 1e-7 , kwargs... )
263
- solver, starts = prob. solver_and_starts
264
- sol = HomotopyContinuation. solve (solver, starts; show_progress, kwargs... )
345
+ if prob. solver_and_starts === nothing
346
+ sol = HomotopyContinuation. solve (
347
+ prob. homotopy_continuation_system; show_progress, kwargs... )
348
+ else
349
+ solver, starts = prob. solver_and_starts
350
+ sol = HomotopyContinuation. solve (solver, starts; show_progress, kwargs... )
351
+ end
265
352
realsols = HomotopyContinuation. results (sol; only_real = true )
266
353
if isempty (realsols)
267
354
u = state_values (prob)
0 commit comments