Improve SphericalBessel? rules for reach faster its fixed point
Certain expressions like SphericalBesselJ are implemented as native WL rules. Depending of how we write these rules, we need several steps before reaching a fixed point in the evaluation. For example,
SphericalBesselJ[n_,x_]
is internally defined as
SphericalBesselJ[n_, z_]->Sqrt[Pi / 2] / Sqrt[z] BesselJ[n + 0.5, z]
but, after evaluation, we get
Sqrt[Pi] Sqrt[2] BesselJ[0.5 + n_, x_] / (2 Sqrt[x_])
The output of TraceEvaluation[SphericalBesselJ[n, z]] is detailed below. However, it is more or less evident that a lot of computation could be saved by defining a more suitable rule. Notice also that such a better rule could depend on the context (for example, if BesselJ is called with numeric arguments, the best rule could be different.
TraceEvaluation[SphericalBesselJ[n,x]]
produces
Evaluating: System`SphericalBesselJ[Global`n, Global`x]
Evaluating: System`SphericalBesselJ
Evaluating: Global`n
Evaluating: Global`x
-> System`Times[System`Sqrt[System`Times[System`Pi, System`Power[2, -1]]], System`Power[System`Sqrt[Global`x], -1], System`BesselJ[System`Plus[Global`n, 0.5], Global`x]]
Evaluating: System`Times
Evaluating: System`Sqrt[System`Times[System`Pi, System`Power[2, -1]]]
Evaluating: System`Sqrt
Evaluating: System`Times[System`Pi, System`Power[2, -1]]
Evaluating: System`Times
Evaluating: System`Pi
Evaluating: System`Power[2, -1]
Evaluating: System`Power
-> 1/2
-> System`Power[System`Times[1/2, System`Pi], System`Times[1, System`Power[2, -1]]]
Evaluating: System`Power
Evaluating: System`Times[1/2, System`Pi]
Evaluating: System`Times[1, System`Power[2, -1]]
Evaluating: System`Times
Evaluating: System`Power[2, -1]
Evaluating: System`Power
-> 1/2
-> 1/2
Evaluating: System`Power[System`Pi, 1/2]
Evaluating: System`Power
Evaluating: System`Pi
Evaluating: System`Pi
Evaluating: System`Power[2, 1/2]
Evaluating: System`Power
-> System`Times[1/2, System`Power[System`Pi, 1/2], System`Power[2, 1/2]]
Evaluating: System`Times
Evaluating: System`Power[System`Pi, 1/2]
Evaluating: System`Power[2, 1/2]
Evaluating: System`Power[System`Sqrt[Global`x], -1]
Evaluating: System`Power
Evaluating: System`Sqrt[Global`x]
Evaluating: System`Sqrt
Evaluating: Global`x
-> System`Power[Global`x, System`Times[1, System`Power[2, -1]]]
Evaluating: System`Power
Evaluating: Global`x
Evaluating: System`Times[1, System`Power[2, -1]]
Evaluating: System`Times
Evaluating: System`Power[2, -1]
Evaluating: System`Power
-> 1/2
-> 1/2
Evaluating: Global`x
Evaluating: Global`x
-> System`Power[Global`x, -1/2]
Evaluating: System`Power
Evaluating: Global`x
Evaluating: Global`x
Evaluating: System`BesselJ[System`Plus[Global`n, 0.5], Global`x]
Evaluating: System`BesselJ
Evaluating: System`Plus[Global`n, 0.5]
Evaluating: System`Plus
Evaluating: Global`n
Evaluating: System`N[Global`n, 15]
Evaluating: System`N
Evaluating: Global`n
Evaluating: System`$MinPrecision
-> 0
Evaluating: System`N[0]
Evaluating: System`N
-> System`N[0, System`MachinePrecision]
Evaluating: System`N
Evaluating: System`MachinePrecision
-> 0.0
Evaluating: System`$MaxPrecision
-> System`Infinity
Evaluating: System`Infinity
-> System`DirectedInfinity[1]
Evaluating: System`DirectedInfinity[1]
Evaluating: System`DirectedInfinity
Evaluating: System`Unequal[System`N[System`Abs[1]], 1]
Evaluating: System`Unequal
Evaluating: System`N[System`Abs[1]]
Evaluating: System`N
Evaluating: System`Abs[1]
Evaluating: System`Abs
-> 1
-> System`N[1, System`MachinePrecision]
Evaluating: System`N
Evaluating: System`MachinePrecision
-> 1.0
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]][1.0]
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]]
Evaluating: System`Function
-> System`Not[System`ExactNumberQ[1.0]]
Evaluating: System`Not
Evaluating: System`ExactNumberQ[1.0]
Evaluating: System`ExactNumberQ
-> System`False
Evaluating: System`False
-> System`True
Evaluating: System`True
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]][1]
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]]
Evaluating: System`Function
-> System`Not[System`ExactNumberQ[1]]
Evaluating: System`Not
Evaluating: System`ExactNumberQ[1]
Evaluating: System`ExactNumberQ
-> System`True
Evaluating: System`True
-> System`False
Evaluating: System`False
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]][1.0]
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]]
Evaluating: System`Function
-> System`Not[System`ExactNumberQ[1.0]]
Evaluating: System`Not
Evaluating: System`ExactNumberQ[1.0]
Evaluating: System`ExactNumberQ
-> System`False
Evaluating: System`False
-> System`True
Evaluating: System`True
Evaluating: System`ExactNumberQ[1.0]
Evaluating: System`ExactNumberQ
-> System`False
Evaluating: System`False
Evaluating: System`ExactNumberQ[1]
Evaluating: System`ExactNumberQ
-> System`True
Evaluating: System`True
Evaluating: System`$MaxExtraPrecision
-> System`False
Evaluating: System`False
Evaluating: System`N[15]
Evaluating: System`N
-> System`N[15, System`MachinePrecision]
Evaluating: System`N
Evaluating: System`MachinePrecision
-> 15.0
-> Global`n
Evaluating: Global`n
Evaluating: Global`x
Evaluating: System`Plus[0.5, Global`n]
Evaluating: System`Plus
Evaluating: Global`n
Evaluating: System`N[Global`n, 15]
Evaluating: System`N
Evaluating: Global`n
Evaluating: System`$MinPrecision
-> 0
Evaluating: System`N[0]
Evaluating: System`N
-> System`N[0, System`MachinePrecision]
Evaluating: System`N
Evaluating: System`MachinePrecision
-> 0.0
Evaluating: System`$MaxPrecision
-> System`Infinity
Evaluating: System`Infinity
-> System`DirectedInfinity[1]
Evaluating: System`DirectedInfinity[1]
Evaluating: System`DirectedInfinity
Evaluating: System`Unequal[System`N[System`Abs[1]], 1]
Evaluating: System`Unequal
Evaluating: System`N[System`Abs[1]]
Evaluating: System`N
Evaluating: System`Abs[1]
Evaluating: System`Abs
-> 1
-> System`N[1, System`MachinePrecision]
Evaluating: System`N
Evaluating: System`MachinePrecision
-> 1.0
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]][1.0]
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]]
Evaluating: System`Function
-> System`Not[System`ExactNumberQ[1.0]]
Evaluating: System`Not
Evaluating: System`ExactNumberQ[1.0]
Evaluating: System`ExactNumberQ
-> System`False
Evaluating: System`False
-> System`True
Evaluating: System`True
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]][1]
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]]
Evaluating: System`Function
-> System`Not[System`ExactNumberQ[1]]
Evaluating: System`Not
Evaluating: System`ExactNumberQ[1]
Evaluating: System`ExactNumberQ
-> System`True
Evaluating: System`True
-> System`False
Evaluating: System`False
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]][1.0]
Evaluating: System`Function[System`Not[System`ExactNumberQ[System`Slot[1]]]]
Evaluating: System`Function
-> System`Not[System`ExactNumberQ[1.0]]
Evaluating: System`Not
Evaluating: System`ExactNumberQ[1.0]
Evaluating: System`ExactNumberQ
-> System`False
Evaluating: System`False
-> System`True
Evaluating: System`True
Evaluating: System`ExactNumberQ[1.0]
Evaluating: System`ExactNumberQ
-> System`False
Evaluating: System`False
Evaluating: System`ExactNumberQ[1]
Evaluating: System`ExactNumberQ
-> System`True
Evaluating: System`True
Evaluating: System`$MaxExtraPrecision
-> System`False
Evaluating: System`False
Evaluating: System`N[15]
Evaluating: System`N
-> System`N[15, System`MachinePrecision]
Evaluating: System`N
Evaluating: System`MachinePrecision
-> 15.0
-> Global`n
Evaluating: Global`n
Evaluating: Global`x
Sqrt[Pi] Sqrt[2] BesselJ[0.5 + n, x] / (2 Sqrt[x])
Part of the key in approaching this would be a better and more precise definition of "Certain Expressions".
It seems that one reason WL has attributes associated with functions like "commutative" or "associative" and "listable" is to that this kind of thing can be made use of these in more efficient evaluation.
So we might consider what class of attributes would make this kind of thing automatic.
@mmatera It would be helpful to show the same sequence after you add a transformation rule that improves this. The one where the N[] are distributed inside.
@mmatera It would be helpful if the same sequence after you add a transformation rule.
Do you mean, create an issue and afterward, a PR?
Regarding the other part, I can think about a way to collect and list exhaustively all the WL native rules in Builtin that are not in their final form.
@mmatera It would be helpful if the same sequence after you add a transformation rule.
Do you mean, create an issue and afterward, a PR?
I meant as clarification of this issue. In order to address it in a general way, one would need to understand it better. This means characterizing the this more precisely than "Certain expressions..." and more generally than a specific trace of a specific expression.
Let's add the examples that you asked @rocky. First, what happens if we redefine SphericalBesselJ using the fixed point:
In[1]:= MySphericalBesselJ[n_,x_]:=Sqrt[Pi] Sqrt[2] BesselJ[1/2 + n, x] / (2 Sqrt[x]);
In[2]:= TraceEvaluation[MySphericalBesselJ[n,x]]
This is the output:
Evaluating: Global`MySphericalBesselJ[Global`n, Global`x]
Evaluating: Global`MySphericalBesselJ
Evaluating: Global`n
Evaluating: Global`x
-> System`Times[System`Sqrt[System`Pi], System`Sqrt[2], System`BesselJ[System`Plus[System`Rational[1, 2], Global`n], Global`x], System`Power[System`Times[2, System`Sqrt[Global`x]], -1]]
Evaluating: System`Times
Evaluating: System`Sqrt[System`Pi]
Evaluating: System`Sqrt
Evaluating: System`Pi
-> System`Power[System`Pi, System`Times[1, System`Power[2, -1]]]
Evaluating: System`Power
Evaluating: System`Pi
Evaluating: System`Times[1, System`Power[2, -1]]
Evaluating: System`Times
Evaluating: System`Power[2, -1]
Evaluating: System`Power
-> 1/2
-> 1/2
Evaluating: System`Pi
Evaluating: System`Sqrt[2]
Evaluating: System`Sqrt
-> System`Power[2, System`Times[1, System`Power[2, -1]]]
Evaluating: System`Power
Evaluating: System`Times[1, System`Power[2, -1]]
Evaluating: System`Times
Evaluating: System`Power[2, -1]
Evaluating: System`Power
-> 1/2
-> 1/2
Evaluating: System`BesselJ[System`Plus[System`Rational[1, 2], Global`n], Global`x]
Evaluating: System`BesselJ
Evaluating: System`Plus[System`Rational[1, 2], Global`n]
Evaluating: System`Plus
Evaluating: System`Rational[1, 2]
Evaluating: System`Rational
-> 1/2
Evaluating: Global`n
Evaluating: Global`x
Evaluating: System`Plus[1/2, Global`n]
Evaluating: System`Plus
Evaluating: Global`n
Evaluating: Global`x
Evaluating: System`Power[System`Times[2, System`Sqrt[Global`x]], -1]
Evaluating: System`Power
Evaluating: System`Times[2, System`Sqrt[Global`x]]
Evaluating: System`Times
Evaluating: System`Sqrt[Global`x]
Evaluating: System`Sqrt
Evaluating: Global`x
-> System`Power[Global`x, System`Times[1, System`Power[2, -1]]]
Evaluating: System`Power
Evaluating: Global`x
Evaluating: System`Times[1, System`Power[2, -1]]
Evaluating: System`Times
Evaluating: System`Power[2, -1]
Evaluating: System`Power
-> 1/2
-> 1/2
Evaluating: Global`x
Evaluating: System`Power[Global`x, -1/2]
Evaluating: System`Power
Evaluating: Global`x
Evaluating: Global`x
-> System`Times[1/2, System`Power[Global`x, -1/2]]
Evaluating: System`Times
Evaluating: System`Power[Global`x, -1/2]
Out[2]= Sqrt[Pi] Sqrt[2] BesselJ[1 / 2 + n, x] / (2 Sqrt[x])
which is much shorter, but still takes a "trivial" step of evaluation.
For N[SphericalBesselJ[1,1/2]], a better rule could be
N[SphericalBesselJ[n_?NumberQ,x_?NumberQ],prec_]:=Sqrt[N[Pi,prec_]/ 2] N[BesselJ[n +Rational[1,2], z]/ Sqrt[z],prec]
that would avoid some reevaluations.
In any case, it would be still much faster to implement the rule directly in Python.