@@ -330,9 +330,9 @@ def summand(element, index):
330330 Expression (
331331 SymbolDerivative ,
332332 * (
333- [Integer0 ] * (index )
334- + [Integer1 ]
335- + [Integer0 ] * (len (f .elements ) - index - 1 )
333+ [Integer0 ] * (index ) +
334+ [Integer1 ] +
335+ [Integer0 ] * (len (f .elements ) - index - 1 )
336336 ),
337337 ),
338338 f .head ,
@@ -664,8 +664,8 @@ def eval(self, f, x, x0, evaluation: Evaluation, options: dict):
664664
665665 # Determine the "jacobian"s
666666 if (
667- method in ("Newton" , "Automatic" )
668- and options ["System`Jacobian" ] is SymbolAutomatic
667+ method in ("Newton" , "Automatic" ) and
668+ options ["System`Jacobian" ] is SymbolAutomatic
669669 ):
670670
671671 def diff (evaluation ):
@@ -1321,16 +1321,16 @@ class NIntegrate(Builtin):
13211321 messages = {
13221322 "bdmtd" : "The Method option should be a built-in method name." ,
13231323 "inumr" : (
1324- "The integrand `1` has evaluated to non-numerical "
1325- + "values for all sampling points in the region "
1326- + "with boundaries `2`"
1324+ "The integrand `1` has evaluated to non-numerical " +
1325+ "values for all sampling points in the region " +
1326+ "with boundaries `2`"
13271327 ),
13281328 "nlim" : "`1` = `2` is not a valid limit of integration." ,
13291329 "ilim" : "Invalid integration variable or limit(s) in `1`." ,
13301330 "mtdfail" : (
1331- "The specified method failed to return a "
1332- + "number. Falling back into the internal "
1333- + "evaluator."
1331+ "The specified method failed to return a " +
1332+ "number. Falling back into the internal " +
1333+ "evaluator."
13341334 ),
13351335 "cmpint" : ("Integration over a complex domain is not " + "implemented yet" ),
13361336 }
@@ -1373,10 +1373,10 @@ class NIntegrate(Builtin):
13731373
13741374 messages .update (
13751375 {
1376- "bdmtd" : "The Method option should be a "
1377- + "built-in method name in {`"
1378- + "`, `" .join (list (methods ))
1379- + "`}. Using `Automatic`"
1376+ "bdmtd" : "The Method option should be a " +
1377+ "built-in method name in {`" +
1378+ "`, `" .join (list (methods )) +
1379+ "`}. Using `Automatic`"
13801380 }
13811381 )
13821382
@@ -1396,7 +1396,7 @@ def eval_with_func_domain(
13961396 elif isinstance (method , Symbol ):
13971397 method = method .get_name ()
13981398 # strip context
1399- method = method [method .rindex ("`" ) + 1 :]
1399+ method = method [method .rindex ("`" ) + 1 :]
14001400 else :
14011401 evaluation .message ("NIntegrate" , "bdmtd" , method )
14021402 return
@@ -2235,146 +2235,157 @@ def eval(self, eqs, vars, evaluation: Evaluation):
22352235 vars = [vars ]
22362236 for var in vars :
22372237 if (
2238- (isinstance (var , Atom ) and not isinstance (var , Symbol ))
2239- or head_name in ("System`Plus" , "System`Times" , "System`Power" ) # noqa
2240- or A_CONSTANT & var .get_attributes (evaluation .definitions )
2238+ (isinstance (var , Atom ) and not isinstance (var , Symbol )) or
2239+ head_name in ("System`Plus" , "System`Times" , "System`Power" ) or # noqa
2240+ A_CONSTANT & var .get_attributes (evaluation .definitions )
22412241 ):
22422242
22432243 evaluation .message ("Solve" , "ivar" , vars_original )
22442244 return
2245- if eqs .get_head_name () in ("System`List" , "System`And" ):
2246- eq_list = eqs .elements
2247- else :
2248- eq_list = [eqs ]
2249- sympy_conditions = []
2250- sympy_eqs = []
2251- sympy_denoms = []
2252- for eq in eq_list :
2253- if eq is SymbolTrue :
2254- pass
2255- elif eq is SymbolFalse :
2256- return ListExpression ()
2257- elif not eq .has_form ("Equal" , 2 ):
2258- sympy_conditions .append (eq .to_sympy ())
2259- else :
2260- left , right = eq .elements
2261- left = left .to_sympy ()
2262- right = right .to_sympy ()
2263- if left is None or right is None :
2264- return
2265- eq = left - right
2266- eq = sympy .together (eq )
2267- eq = sympy .cancel (eq )
2268- sympy_eqs .append (eq )
2269- numer , denom = eq .as_numer_denom ()
2270- sympy_denoms .append (denom )
2271-
2272- if not sympy_eqs :
2273- evaluation .message ("Solve" , "eqf" , eqs )
2274- return
22752245
22762246 vars_sympy = [var .to_sympy () for var in vars ]
22772247 if None in vars_sympy :
2248+ evaluation .message ("Solve" , "ivar" )
22782249 return
2279-
2280- # delete unused variables to avoid SymPy's
2281- # PolynomialError: Not a zero-dimensional system
2282- # in e.g. Solve[x^2==1&&z^2==-1,{x,y,z}]
2283- all_vars = vars [:]
2284- all_vars_sympy = vars_sympy [:]
2285- vars = []
2286- vars_sympy = []
2287- for var , var_sympy in zip (all_vars , all_vars_sympy ):
2288- pattern = Pattern .create (var )
2289- if not eqs .is_free (pattern , evaluation ):
2290- vars .append (var )
2291- vars_sympy .append (var_sympy )
2292-
2293- def transform_dict (sols ):
2294- if not sols :
2295- yield sols
2296- for var , sol in sols .items ():
2297- rest = sols .copy ()
2298- del rest [var ]
2299- rest = transform_dict (rest )
2300- if not isinstance (sol , (tuple , list )):
2301- sol = [sol ]
2302- if not sol :
2303- for r in rest :
2304- yield r
2305- else :
2306- for r in rest :
2307- for item in sol :
2308- new_sols = r .copy ()
2309- new_sols [var ] = item
2310- yield new_sols
2311- break
2312-
2313- def transform_solution (sol ):
2314- if not isinstance (sol , dict ):
2315- if not isinstance (sol , (list , tuple )):
2316- sol = [sol ]
2317- sol = dict (list (zip (vars_sympy , sol )))
2318- return transform_dict (sol )
2319-
2320- if not sympy_eqs :
2321- sympy_eqs = True
2322- elif len (sympy_eqs ) == 1 :
2323- sympy_eqs = sympy_eqs [0 ]
2324-
2325- try :
2326- if isinstance (sympy_eqs , bool ):
2327- result = sympy_eqs
2250+ all_var_tuples = list (zip (vars , vars_sympy ))
2251+
2252+ def cut_var_dimension (expressions : Expression | list [Expression ]):
2253+ '''delete unused variables to avoid SymPy's PolynomialError
2254+ : Not a zero-dimensional system in e.g. Solve[x^2==1&&z^2==-1,{x,y,z}]'''
2255+ if not isinstance (expressions , list ):
2256+ expressions = [expressions ]
2257+ subset_vars = set ()
2258+ subset_vars_sympy = set ()
2259+ for var , var_sympy in all_var_tuples :
2260+ pattern = Pattern .create (var )
2261+ for equation in expressions :
2262+ if not equation .is_free (pattern , evaluation ):
2263+ subset_vars .add (var )
2264+ subset_vars_sympy .add (var_sympy )
2265+ return subset_vars , subset_vars_sympy
2266+
2267+ def solve_sympy (equations : Expression | list [Expression ]):
2268+ if not isinstance (equations , list ):
2269+ equations = [equations ]
2270+ equations_sympy = []
2271+ denoms_sympy = []
2272+ subset_vars , subset_vars_sympy = cut_var_dimension (equations )
2273+ for equation in equations :
2274+ if equation is SymbolTrue :
2275+ continue
2276+ elif equation is SymbolFalse :
2277+ return []
2278+ elements = equation .elements
2279+ for left , right in [(elements [index ], elements [index + 1 ]) for index in range (len (elements ) - 1 )]:
2280+ # ↑ to deal with things like a==b==c==d
2281+ left = left .to_sympy ()
2282+ right = right .to_sympy ()
2283+ if left is None or right is None :
2284+ return []
2285+ equation_sympy = left - right
2286+ equation_sympy = sympy .together (equation_sympy )
2287+ equation_sympy = sympy .cancel (equation_sympy )
2288+ numer , denom = equation_sympy .as_numer_denom ()
2289+ denoms_sympy .append (denom )
2290+ try :
2291+ results = sympy .solve (equations_sympy , subset_vars_sympy , dict = True ) # no transform needed with dict=True
2292+ # Filter out results for which denominator is 0
2293+ # (SymPy should actually do that itself, but it doesn't!)
2294+ results = [
2295+ sol
2296+ for sol in results
2297+ if all (sympy .simplify (denom .subs (sol )) != 0 for denom in denoms_sympy )
2298+ ]
2299+ return results
2300+ except sympy .PolynomialError :
2301+ # raised for e.g. Solve[x^2==1&&z^2==-1,{x,y,z}] when not deleting
2302+ # unused variables beforehand
2303+ return []
2304+ except NotImplementedError :
2305+ return []
2306+ except TypeError as exc :
2307+ if str (exc ).startswith ("expected Symbol, Function or Derivative" ):
2308+ evaluation .message ("Solve" , "ivar" , vars_original )
2309+
2310+ def solve_recur (expression : Expression ):
2311+ '''solve And, Or and List within the scope of sympy,
2312+ but including the translation from Mathics to sympy
2313+
2314+ returns:
2315+ solutions: a list of sympy solution dictionaries
2316+ conditions: a sympy condition object
2317+
2318+ note:
2319+ for And and List, should always return either (solutions, None) or ([], conditions)
2320+ for Or, all combinations are possible. if Or is root, should be handled outside'''
2321+ head = expression .get_head_name ()
2322+ if head in ("System`And" , "System`List" ):
2323+ solutions = []
2324+ equations : list [Expression ] = []
2325+ inequations = []
2326+ for child in expression .elements :
2327+ if child .has_form ("Equal" , 2 ):
2328+ equations .append (child )
2329+ elif child .get_head_name () in ("System`And" , "System`Or" ):
2330+ sub_solution , sub_condition = solve_recur (child )
2331+ solutions .extend (sub_solution )
2332+ if sub_condition is not None :
2333+ inequations .append (sub_condition )
2334+ else :
2335+ inequations .append (child .to_sympy ())
2336+ solutions .extend (solve_sympy (equations ))
2337+ conditions = sympy .And (* inequations )
2338+ result = [sol for sol in solutions if conditions .subs (sol )]
2339+ return result , None if solutions else conditions
2340+ else : # should be System`Or then
2341+ assert head == "System`Or"
2342+ solutions = []
2343+ conditions = []
2344+ for child in expression .elements :
2345+ if child .has_form ("Equal" , 2 ):
2346+ solutions .extend (solve_sympy (child ))
2347+ elif child .get_head_name () in ("System`And" , "System`Or" ): # List wouldn't be in here
2348+ sub_solution , sub_condition = solve_recur (child )
2349+ solutions .extend (sub_solution )
2350+ if sub_condition is not None :
2351+ conditions .append (sub_condition )
2352+ else :
2353+ # SymbolTrue and SymbolFalse are allowed here since it's subtree context
2354+ # FIXME: None is not allowed, not sure what to do here
2355+ conditions .append (child .to_sympy ())
2356+ conditions = sympy .Or (* conditions )
2357+ return solutions , conditions
2358+
2359+ if eqs .get_head_name () in ("System`List" , "System`And" , "System`Or" ):
2360+ solutions , conditions = solve_recur (eqs )
2361+ # non True conditions are only accepted in subtrees, not root
2362+ if conditions is not None :
2363+ evaluation .message ("Solve" , "fulldim" )
2364+ return ListExpression (ListExpression ())
2365+ else :
2366+ if eqs .has_form ("Equal" , 2 ):
2367+ solutions = solve_sympy (eqs )
23282368 else :
2329- result = sympy .solve (sympy_eqs , vars_sympy )
2330- if not isinstance (result , list ):
2331- result = [result ]
2332- if isinstance (result , list ) and len (result ) == 1 and result [0 ] is True :
2369+ evaluation .message ("Solve" , "fulldim" )
23332370 return ListExpression (ListExpression ())
2334- if result == [None ]:
2335- return ListExpression ()
2336- results = []
2337- for sol in result :
2338- results .extend (transform_solution (sol ))
2339- result = results
2340- # filter with conditions before further translation
2341- conditions = sympy .And (* sympy_conditions )
2342- result = [sol for sol in result if conditions .subs (sol )]
2343-
2344- if any (
2345- sol and any (var not in sol for var in all_vars_sympy ) for sol in result
2346- ):
2347- evaluation .message ("Solve" , "svars" )
23482371
2349- # Filter out results for which denominator is 0
2350- # (SymPy should actually do that itself, but it doesn't!)
2351- result = [
2352- sol
2353- for sol in result
2354- if all (sympy .simplify (denom .subs (sol )) != 0 for denom in sympy_denoms )
2355- ]
2356-
2357- return ListExpression (
2358- * (
2359- ListExpression (
2360- * (
2361- Expression (SymbolRule , var , from_sympy (sol [var_sympy ]))
2362- for var , var_sympy in zip (vars , vars_sympy )
2363- if var_sympy in sol
2364- ),
2365- )
2366- for sol in result
2367- ),
2368- )
2369- except sympy .PolynomialError :
2370- # raised for e.g. Solve[x^2==1&&z^2==-1,{x,y,z}] when not deleting
2371- # unused variables beforehand
2372- pass
2373- except NotImplementedError :
2374- pass
2375- except TypeError as exc :
2376- if str (exc ).startswith ("expected Symbol, Function or Derivative" ):
2377- evaluation .message ("Solve" , "ivar" , vars_original )
2372+ if any (
2373+ sol and any (var not in sol for var in vars_sympy ) for sol in solutions
2374+ ):
2375+ evaluation .message ("Solve" , "svars" )
2376+
2377+ return ListExpression (
2378+ * (
2379+ ListExpression (
2380+ * (
2381+ Expression (SymbolRule , var , from_sympy (sol [var_sympy ]))
2382+ for var , var_sympy in zip (vars , all_var_tuples )
2383+ if var_sympy in sol
2384+ ),
2385+ )
2386+ for sol in solutions
2387+ ),
2388+ )
23782389
23792390
23802391# Auxiliary routines. Maybe should be moved to another module.
0 commit comments