@@ -15,42 +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)
3593 is_pow_integer = symtype (p) <: Integer
3694 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
42100 end
101+
43102 exponent_has_unknowns = contains_variable (p, wrt)
44103 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)
46106 end
47- base_polynomial = is_polynomial ( b, wrt)
107+ base_polynomial = is_polynomial! (data, b, wrt)
48108 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)
50111 end
51112 return base_polynomial && ! exponent_has_unknowns && is_pow_integer
52113 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)
54116 return false
55117end
56118
@@ -179,21 +241,39 @@ Create a `HomotopyContinuationProblem` from a `NonlinearSystem` with polynomial
179241The problem will be solved by HomotopyContinuation.jl. The resultant `NonlinearSolution`
180242will contain the polynomial root closest to the point specified by `u0map` (if real roots
181243exist 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`.
182252"""
183253function MTK. HomotopyContinuationProblem (
184254 sys:: NonlinearSystem , u0map, parammap = nothing ; eval_expression = false ,
185- eval_module = ModelingToolkit, kwargs... )
255+ eval_module = ModelingToolkit, warn_parametric_exponent = true , kwargs... )
186256 if ! iscomplete (sys)
187257 error (" A completed `NonlinearSystem` is required. Call `complete` or `structural_simplify` on the system before creating a `HomotopyContinuationProblem`" )
188258 end
189259
190260 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)
192267
193268 denoms = []
269+ has_parametric_exponents = false
194270 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))
197277 end
198278 num, den = handle_rational_polynomials (eq. rhs - eq. lhs, dvs)
199279
@@ -212,6 +292,9 @@ function MTK.HomotopyContinuationProblem(
212292 end
213293
214294 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
215298
216299 nlfn, u0, p = MTK. process_SciMLProblem (NonlinearFunction{true }, sys2, u0map, parammap;
217300 jac = true , eval_expression, eval_module)
@@ -223,29 +306,49 @@ function MTK.HomotopyContinuationProblem(
223306
224307 obsfn = MTK. ObservedFunctionCache (sys; eval_expression, eval_module)
225308
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)
227323end
228324
229325"""
230326$(TYPEDSIGNATURES)
231327
232328Solve 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
235330`HomotopyContinuation.jl` will be available in the `.original` field of the returned
236331`NonlinearSolution`.
237332
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`.
240338
241339Extra keyword arguments:
242340- `denominator_abstol`: In case `prob` is solving a rational function, roots which cause
243341 the denominator to be below `denominator_abstol` will be discarded.
244342"""
245343function CommonSolve. solve (prob:: MTK.HomotopyContinuationProblem ,
246344 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
249352 realsols = HomotopyContinuation. results (sol; only_real = true )
250353 if isempty (realsols)
251354 u = state_values (prob)
0 commit comments