4343 PrecisionReal ,
4444 Rational ,
4545 Real ,
46+ String ,
4647 Symbol ,
4748 SymbolFalse ,
4849 SymbolTrue ,
@@ -256,7 +257,38 @@ def apply_other(self, expr, prec, evaluation):
256257 )
257258 return Expression (head , * leaves )
258259
260+ def _scipy_interface (integrator , options_map , mandatory = None , adapt_func = None ):
261+ """
262+ This function provides a proxy for scipy.integrate
263+ functions, adapting the parameters.
264+ """
265+ def _scipy_proxy_func_filter (fun , a , b , ** opts ):
266+ native_opts = {}
267+ if mandatory :
268+ native_opts .update (mandatory )
269+ for opt , val in opts .items ():
270+ native_opt = options_map .get (opt , None )
271+ if native_opt :
272+ if native_opt [1 ]:
273+ val = native_opt [1 ](val )
274+ native_opts [native_opt [0 ]] = val
275+ return adapt_func (integrator (fun , a , b , ** native_opts ))
276+
277+ def _scipy_proxy_func (fun , a , b , ** opts ):
278+ native_opts = {}
279+ if mandatory :
280+ native_opts .update (mandatory )
281+ for opt , val in opts .items ():
282+ native_opt = options_map .get (opt , None )
283+ if native_opt :
284+ if native_opt [1 ]:
285+ val = native_opt [1 ](val )
286+ native_opts [native_opt [0 ]] = val
287+ return integrator (fun , a , b , ** native_opts )
288+
289+ return _scipy_proxy_func_filter if adapt_func else _scipy_proxy_func
259290
291+
260292
261293def _internal_adaptative_simpsons_rule (f , a , b , ** opts ):
262294 """
@@ -352,17 +384,22 @@ def _fubini(func, ranges, **opts):
352384 if tol is None :
353385 opts ["tol" ] = 1.e-10
354386 tol = 1.e-10
355-
387+
356388 if len (ranges ) > 1 :
357389 def subintegral (* u ):
358390 def ff (* z ):
359- return func (* (u + z ))
360-
361- return _fubini (ff , ranges [1 :], ** opts )[0 ]
391+ val = func (* (u + z ))
392+ return val
393+
394+ val = _fubini (ff , ranges [1 :], ** opts )[0 ]
395+ return val
396+
362397 opts ["tol" ] = 4. * tol
363- return integrator (subintegral , a , b , ** opts )
398+ val = integrator (subintegral , a , b , ** opts )
399+ return val
364400 else :
365- return integrator (func , a , b , ** opts )
401+ val = integrator (func , a , b , ** opts )
402+ return val
366403
367404
368405class NIntegrate (Builtin ):
@@ -373,6 +410,7 @@ class NIntegrate(Builtin):
373410 </dl>
374411
375412 >> Table[1./NIntegrate[x^k,{x,0,1},Tolerance->1*^-6], {k,0,6}]
413+ : The especified method failed to return a number. Falling back into the internal evaluator.
376414 = {1., 2., 3., 4., 5., 6., 7.}
377415 >> NIntegrate[Exp[-x],{x,0,Infinity},Tolerance->1*^-6]
378416 = 1.
@@ -381,6 +419,11 @@ class NIntegrate(Builtin):
381419 >> NIntegrate[Exp[-x^2/2.],{x,-Infinity, Infinity},Tolerance->1*^-6]
382420 = 2.50663
383421
422+ >> NIntegrate[1 / z, {z, -1 - I, 1 - I, 1 + I, -1 + I, -1 - I}, Tolerance->1.*^-4]
423+ : Integration over a complex domain is not implemented yet
424+ = NIntegrate[1 / z, {z, -1 - I, 1 - I, 1 + I, -1 + I, -1 - I}, Tolerance -> 0.0001]
425+ # = 6.2832 I
426+
384427 Integrate singularities with weak divergences
385428 >> Table[NIntegrate[x^(1./k-1.),{x,0,1.},Tolerance->1*^-6], {k,1,7.}]
386429 = {1., 2., 3., 4., 5., 6., 7.}
@@ -394,35 +437,51 @@ class NIntegrate(Builtin):
394437# 'NIntegrate[f,{x,a,b}, "AccuracyGoal"->prec]'
395438# }
396439
397- # messages = {'nnintegr':"The integrand `1` is not a numeric function",
398- # 'nninflim': "The inferior limit `1` is not numeric",
399- # 'nninflim': "The superior limit `1` is not numeric",
400- # }
401-
402- options = {'"Method"' : '"Automatic"' ,
440+ messages = {'bdmtd' : "The Method option should be a built-in method name." ,
441+ 'inumr' : " The integrand `1` has evaluated to non-numerical values for all sampling points in the region with boundaries `2`" ,
442+ 'nlim' : '`1` = `2` is not a valid limit of integration.' ,
443+ 'ilim' : 'Invalid integration variable or limit(s) in `1`.' ,
444+ 'mtdfail' : "The especified method failed to return a number. Falling back into the internal evaluator." ,
445+ 'cmpint' : "Integration over a complex domain is not implemented yet" ,
446+ }
447+
448+ options = {'Method' : '"Automatic"' ,
403449 'Tolerance' : '1*^-10' ,
404450 'Accuracy' : '1*^-10' ,
405451 'MaxRecursion' : '10' ,
406452 }
407453
408454
409- methods = {'Automatic' : None ,}
455+ methods = {'Automatic' : ( None , False ) ,}
410456
411457 def __init__ (self , * args , ** kwargs ):
412458 super ().__init__ (* args , ** kwargs )
413- self .methods ["Internal" ] = _internal_adaptative_simpsons_rule
459+ self .methods ["Internal" ] = ( _internal_adaptative_simpsons_rule , False )
414460 try :
415- from scipy .integrate import (romberg , quad )
461+ from scipy .integrate import (romberg , quad , nquad )
416462 print ("Using scipy.integrate for numeric integration" )
417- self .methods ["Quadrature" ] = quad
418- self .methods ["Quadrature" ] = romberg
419- self .methods ["Automatic" ] = self .methods ["Internal" ]
463+ self .methods ["NQuadrature" ] = (_scipy_interface (nquad ,
464+ {}, {"full_output" : 1 },
465+ lambda res : (res [0 ], res [1 ])), True )
466+ self .methods ["Quadrature" ] = (_scipy_interface (quad ,
467+ {"tol" : ("epsabs" , None ),
468+ "maxrec" : ("limit" ,lambda maxrec : int (2 ** maxrec ))
469+ }, {"full_output" : 1 },
470+ lambda res : (res [0 ], res [1 ])), False )
471+ self .methods ["Romberg" ] = (_scipy_interface (romberg ,
472+ {"tol" : ("tol" , None ),
473+ "maxrec" : ("divmax" , None )
474+ }, None , lambda x : (x , np .nan ) ), False )
475+ self .methods ["Automatic" ] = self .methods ["Quadrature" ]
420476 except Exception as e :
421- print (e )
422477 print ("Scipy not available. Using internal integrator" )
423478 self .methods ["Automatic" ] = self .methods ["Internal" ]
424479 self .methods ["Simpson" ] = self .methods ["Internal" ]
425480
481+ self .messages ["bdmtd" ] = "The Method option should be a built-in method name in {`" + \
482+ "`, `" .join (list (self .methods )) + \
483+ "`}. Using `Automatic`"
484+
426485 @staticmethod
427486 def decompose_domain (interval , evaluation ):
428487 if interval .has_form ("System`Sequence" , 1 , None ):
@@ -432,38 +491,59 @@ def decompose_domain(interval, evaluation):
432491 if inner_interval :
433492 intervals .append (inner_interval )
434493 else :
435- evaluation .message ("Malformed interval" )
494+ evaluation .message ("ilim" , leaf )
436495 return None
437496 return intervals
438497
439- if interval .has_form ('System`List' ,3 , None ):
498+ if interval .has_form ('System`List' , 3 , None ):
440499 intervals = []
441500 intvar = interval .leaves [0 ]
442501 if not isinstance (intvar , Symbol ):
502+ evaluation .message ("ilim" , interval )
443503 return None
444504 boundaries = [a for a in interval .leaves [1 :]]
505+ if any ([b .get_head_name () == "System`Complex" for b in boundaries ]):
506+ intvar = Expression ("List" , intvar , Expression ("Blank" , Symbol ("Complex" )))
445507 for i in range (len (boundaries )- 1 ):
446- intervals .append ((boundaries [i ],boundaries [i + 1 ]))
508+ intervals .append ((boundaries [i ], boundaries [i + 1 ]))
447509 if len (intervals )> 0 :
448510 return (intvar , intervals )
449511
450- return None
451- evaluation .message ("Domain not properly formatted" )
512+
513+ evaluation .message ("ilim" , interval )
452514 return None
453515
454516 def apply_2 (self , func , domain , evaluation , options ):
455517 "NIntegrate[func_, domain__, OptionsPattern[%(name)s]]"
456- method = options ['System`"Method"' ].value
518+ method = options ['System`Method' ].evaluate (evaluation )
519+ method_options = {}
520+ if method .has_form ("System`List" ,2 ):
521+ method = method .leaves [0 ]
522+ method_options .update (method .leaves [1 ].get_option_values ())
523+
524+ if isinstance (method , String ):
525+ method = method .value
526+ elif isinstance (method , Symbol ):
527+ method = method .get_name ()
528+ else :
529+ evaluation .message ("NIntegrate" , "bdmtd" , method )
530+ return
457531 tolerance = options ['System`Tolerance' ].evaluate (evaluation )
458532 tolerance = float (tolerance .value )
459533 accuracy = options ['System`Accuracy' ].evaluate (evaluation )
460534 accuracy = accuracy .value
461535 maxrecursion = options ['System`MaxRecursion' ].evaluate (evaluation )
462536 maxrecursion = maxrecursion .value
463537
464- nintegrate_method = self .methods .get (method )
538+ nintegrate_method = self .methods .get (method , None )
465539 if nintegrate_method is None :
540+ evaluation .message ("NIntegrate" ,"bdmtd" , method )
466541 nintegrate_method = self .methods .get ("Automatic" )
542+
543+ if type (nintegrate_method ) is tuple :
544+ nintegrate_method , is_multidimensional = nintegrate_method
545+ else :
546+ is_multidimensional = False
467547
468548 domain = self .decompose_domain (domain , evaluation )
469549 if not domain :
@@ -472,12 +552,18 @@ def apply_2(self, func, domain, evaluation, options):
472552 domain = [domain ]
473553
474554 coords = [axis [0 ] for axis in domain ]
555+ # If any of the points in the integration domain is complex, stop the evaluation...
556+ if any ([c .get_head_name () == "System`List" for c in coords ]):
557+ evaluation .message ("NIntegrate" , "cmpint" )
558+ return
559+
475560 intvars = Expression ("List" , * coords )
476561 integrand = Expression ("Compile" , intvars , func ).evaluate (evaluation )
562+
477563 if len (integrand .leaves )>= 3 :
478564 integrand = integrand .leaves [2 ].cfunc
479565 else :
480- print ( func , " could not be compiled" )
566+ evaluation . message ( "inumer" , func , domain )
481567 return
482568 results = []
483569 for subdomain in product (* [axis [1 ] for axis in domain ]):
@@ -508,28 +594,39 @@ def apply_2(self, func, domain, evaluation, options):
508594 coordtransform .append ((np .arctanh , lambda u :1. / (1. - u ** 2 )))
509595 else :
510596 if not b .is_numeric ():
511- evaluation .message ("Boundary is not numeric." )
512- return SymbolFailed
597+ evaluation .message ("nlim" , coords [ i ], b )
598+ return
513599 z = a .leaves [0 ].value
514600 b = b .value
515601 subdomain2 .append ( [machine_epsilon , 1. ])
516602 coordtransform .append ((lambda u : b - z + z / u , lambda u : - z * u ** (- 2. )))
517603 elif b .get_head_name () == 'System`DirectedInfinity' :
518604 if not a .is_numeric ():
519- evaluation .message ("Boundary is not numeric." )
520- return SymbolFailed
605+ evaluation .message ("nlim" , coords [ i ], a )
606+ return
521607 a = a .value
522608 z = b .leaves [0 ].value
523609 subdomain2 .append ([machine_epsilon , 1. ])
524610 coordtransform .append ((lambda u : a - z + z / u , lambda u : z * u ** (- 2. )))
525611 elif a .is_numeric () and b .is_numeric ():
526- a = Expression ("N" , a ).evaluate (evaluation ).value
527- b = Expression ("N" , b ).evaluate (evaluation ).value
528- subdomain2 .append ([a , b ])
529- coordtransform .append (None )
612+ if "System`Complex" in (a .get_head_name (), b .get_head_name ()):
613+ evaluation .message ("NIntegrate" , "Integration over a complex domain is not implemented yet" )
614+ return
615+ # a = a.to_python()
616+ # b = b.to_python()
617+ # dab = b - a
618+ # subdomain2.append([0, 1.])
619+ # coordtransform.append((lambda u: a + dab * u, lambda u: dab))
620+ else :
621+ a = Expression ("N" , a ).evaluate (evaluation ).value
622+ b = Expression ("N" , b ).evaluate (evaluation ).value
623+ subdomain2 .append ([a , b ])
624+ coordtransform .append (None )
530625 else :
531- evaluation .message ("Boundary is not numeric." )
532- return SymbolFailed
626+ for x in (a ,b ):
627+ if not x .is_numeric ():
628+ evaluation .message ("nlim" , coords [i ], x )
629+ return
533630
534631 if nulldomain :
535632 continue
@@ -539,19 +636,34 @@ def apply_2(self, func, domain, evaluation, options):
539636 for i , x in enumerate (coordtransform )])
540637 * np .prod ([ jac [1 ](u [i ] )
541638 for i , jac in enumerate (coordtransform ) if jac ]))
542- opts = {#" " : accuracy,
639+ opts = {"acur " : accuracy ,
543640 "tol" : tolerance ,
544- # "maxrec" : maxrecursion,
641+ "maxrec" : maxrecursion ,
545642 }
546- if len (subdomain2 ) > 1 :
547- val = _fubini (func2 , subdomain2 ,
643+ opts .update (method_options )
644+ try :
645+ if len (subdomain2 ) > 1 :
646+ if is_multidimensional :
647+ nintegrate_method (func2 , subdomain2 , ** opts )
648+ else :
649+ val = _fubini (func2 , subdomain2 ,
548650 integrator = nintegrate_method , ** opts )
549- else :
550- val = nintegrate_method (func2 , * (subdomain2 [0 ]), ** opts )
651+ else :
652+ val = nintegrate_method (func2 , * (subdomain2 [0 ]), ** opts )
653+ except Exception as e :
654+ val = None
655+
656+ if val is None :
657+ evaluation .message ("NIntegrate" , "mtdfail" )
658+ if len (subdomain2 ) > 1 :
659+ val = _fubini (func2 , subdomain2 ,
660+ integrator = _internal_adaptative_simpsons_rule , ** opts )
661+ else :
662+ val = _internal_adaptative_simpsons_rule (func2 , * (subdomain2 [0 ]), ** opts )
551663 results .append (val )
664+
552665 result = sum ([r [0 ] for r in results ])
553666 error = sum ([r [1 ] for r in results ])
554- # print("result=", result, " error=",error)
555667 return from_python (result )
556668
557669class MachinePrecision (Predefined ):
0 commit comments