@@ -15,42 +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)
35
93
is_pow_integer = symtype (p) <: Integer
36
94
if ! is_pow_integer
37
- if symbolic_type (p) == NotSymbolic ( )
38
- @warn " In $x : Exponent $p is not an integer "
39
- else
40
- @warn " In $x : Exponent $p is not an integer. Use `@parameters p::Integer` to declare integer parameters. "
41
- end
95
+ push! (data . non_polynomial_terms, x )
96
+ push! (data . reasons, NonPolynomialReason . NonIntegerExponent)
97
+ end
98
+ if symbolic_type (p) != NotSymbolic ()
99
+ data . has_parametric_exponent = true
42
100
end
101
+
43
102
exponent_has_unknowns = contains_variable (p, wrt)
44
103
if exponent_has_unknowns
45
- @warn " In $x : Exponent $p cannot contain unknowns of the system."
104
+ push! (data. non_polynomial_terms, x)
105
+ push! (data. reasons, NonPolynomialReason. ExponentContainsUnknowns)
46
106
end
47
- base_polynomial = is_polynomial ( b, wrt)
107
+ base_polynomial = is_polynomial! (data, b, wrt)
48
108
if ! base_polynomial
49
- @warn " In $x : Base is not a polynomial"
109
+ push! (data. non_polynomial_terms, x)
110
+ push! (data. reasons, NonPolynomialReason. BaseNotPolynomial)
50
111
end
51
112
return base_polynomial && ! exponent_has_unknowns && is_pow_integer
52
113
end
53
- @warn " In $x : Unrecognized operation $(operation (x)) . Allowed polynomial operations are `*, +, -, ^`"
114
+ push! (data. non_polynomial_terms, x)
115
+ push! (data. reasons, NonPolynomialReason. UnrecognizedOperation)
54
116
return false
55
117
end
56
118
@@ -179,21 +241,39 @@ Create a `HomotopyContinuationProblem` from a `NonlinearSystem` with polynomial
179
241
The problem will be solved by HomotopyContinuation.jl. The resultant `NonlinearSolution`
180
242
will contain the polynomial root closest to the point specified by `u0map` (if real roots
181
243
exist for the system).
244
+
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`.
182
252
"""
183
253
function MTK. HomotopyContinuationProblem (
184
254
sys:: NonlinearSystem , u0map, parammap = nothing ; eval_expression = false ,
185
- eval_module = ModelingToolkit, kwargs... )
255
+ eval_module = ModelingToolkit, warn_parametric_exponent = true , kwargs... )
186
256
if ! iscomplete (sys)
187
257
error (" A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`" )
188
258
end
189
259
190
260
dvs = unknowns (sys)
191
- eqs = equations (sys)
261
+ # we need to consider `full_equations` because observed also should be
262
+ # polynomials (if used in equations) and we don't know if observed is used
263
+ # in denominator.
264
+ # This is not the most efficient, and would be improved significantly with
265
+ # CSE/hashconsing.
266
+ eqs = full_equations (sys)
192
267
193
268
denoms = []
269
+ has_parametric_exponents = false
194
270
eqs2 = map (eqs) do eq
195
- if ! is_polynomial (eq. lhs, dvs) || ! is_polynomial (eq. rhs, dvs)
196
- 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))
197
277
end
198
278
num, den = handle_rational_polynomials (eq. rhs - eq. lhs, dvs)
199
279
@@ -212,6 +292,9 @@ function MTK.HomotopyContinuationProblem(
212
292
end
213
293
214
294
sys2 = MTK. @set sys. eqs = eqs2
295
+ # remove observed equations to avoid adding them in codegen
296
+ MTK. @set! sys2. observed = Equation[]
297
+ MTK. @set! sys2. substitutions = nothing
215
298
216
299
nlfn, u0, p = MTK. process_SciMLProblem (NonlinearFunction{true }, sys2, u0map, parammap;
217
300
jac = true , eval_expression, eval_module)
@@ -223,29 +306,49 @@ function MTK.HomotopyContinuationProblem(
223
306
224
307
obsfn = MTK. ObservedFunctionCache (sys; eval_expression, eval_module)
225
308
226
- return MTK. HomotopyContinuationProblem (u0, mtkhsys, denominator, sys, obsfn)
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
321
+ return MTK. HomotopyContinuationProblem (
322
+ u0, mtkhsys, denominator, sys, obsfn, solver_and_starts)
227
323
end
228
324
229
325
"""
230
326
$(TYPEDSIGNATURES)
231
327
232
328
Solve a `HomotopyContinuationProblem`. Ignores the algorithm passed to it, and always
233
- uses `HomotopyContinuation.jl`. All keyword arguments except the ones listed below are
234
- forwarded to `HomotopyContinuation.solve`. The original solution as returned by
329
+ uses `HomotopyContinuation.jl`. The original solution as returned by
235
330
`HomotopyContinuation.jl` will be available in the `.original` field of the returned
236
331
`NonlinearSolution`.
237
332
238
- All keyword arguments have their default values in HomotopyContinuation.jl, except
239
- `show_progress` which defaults to `false`.
333
+ All keyword arguments except the ones listed below are forwarded to
334
+ `HomotopyContinuation.solve`. Note that the solver and start solutions are precomputed,
335
+ and only keyword arguments related to the solve process are valid. All keyword
336
+ arguments have their default values in HomotopyContinuation.jl, except `show_progress`
337
+ which defaults to `false`.
240
338
241
339
Extra keyword arguments:
242
340
- `denominator_abstol`: In case `prob` is solving a rational function, roots which cause
243
341
the denominator to be below `denominator_abstol` will be discarded.
244
342
"""
245
343
function CommonSolve. solve (prob:: MTK.HomotopyContinuationProblem ,
246
344
alg = nothing ; show_progress = false , denominator_abstol = 1e-7 , kwargs... )
247
- sol = HomotopyContinuation. solve (
248
- prob. homotopy_continuation_system; 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
249
352
realsols = HomotopyContinuation. results (sol; only_real = true )
250
353
if isempty (realsols)
251
354
u = state_values (prob)
0 commit comments