@@ -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