@@ -15,44 +15,104 @@ function contains_variable(x, wrt)
1515 any (y -> occursin (y, x), wrt)
1616end
1717
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+
1875"""
1976$(TYPEDSIGNATURES)
2077
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`.
2280"""
23- function is_polynomial ( x, wrt)
81+ function process_polynomial! (data :: PolynomialData , x, wrt)
2482 x = unwrap (x)
2583 symbolic_type (x) == NotSymbolic () && return true
2684 iscall (x) || return true
2785 contains_variable (x, wrt) || return true
2886 any (isequal (x), wrt) && return true
2987
3088 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))
3290 end
3391 if operation (x) == (^ )
3492 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
3598 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
43100 end
44101
45102 exponent_has_unknowns = contains_variable (p, wrt)
46103 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)
48106 end
49- base_polynomial = is_polynomial ( b, wrt)
107+ base_polynomial = is_polynomial! (data, b, wrt)
50108 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)
52111 end
53112 return base_polynomial && ! exponent_has_unknowns && is_pow_integer
54113 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)
56116 return false
57117end
58118
@@ -182,11 +242,17 @@ The problem will be solved by HomotopyContinuation.jl. The resultant `NonlinearS
182242will contain the polynomial root closest to the point specified by `u0map` (if real roots
183243exist for the system).
184244
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`.
186252"""
187253function MTK. HomotopyContinuationProblem (
188254 sys:: NonlinearSystem , u0map, parammap = nothing ; eval_expression = false ,
189- eval_module = ModelingToolkit, kwargs... )
255+ eval_module = ModelingToolkit, warn_parametric_exponent = true , kwargs... )
190256 if ! iscomplete (sys)
191257 error (" A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`" )
192258 end
@@ -200,9 +266,14 @@ function MTK.HomotopyContinuationProblem(
200266 eqs = full_equations (sys)
201267
202268 denoms = []
269+ has_parametric_exponents = false
203270 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))
206277 end
207278 num, den = handle_rational_polynomials (eq. rhs - eq. lhs, dvs)
208279
@@ -235,7 +306,18 @@ function MTK.HomotopyContinuationProblem(
235306
236307 obsfn = MTK. ObservedFunctionCache (sys; eval_expression, eval_module)
237308
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
239321 return MTK. HomotopyContinuationProblem (
240322 u0, mtkhsys, denominator, sys, obsfn, solver_and_starts)
241323end
@@ -260,8 +342,13 @@ Extra keyword arguments:
260342"""
261343function CommonSolve. solve (prob:: MTK.HomotopyContinuationProblem ,
262344 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
265352 realsols = HomotopyContinuation. results (sol; only_real = true )
266353 if isempty (realsols)
267354 u = state_values (prob)
0 commit comments