Triangular recursions are a class of algorithms that frequently turn up in computational mathematics. These recursions are expressible in the general form
$$T_k^{(n)}=f(T_{k-1}^{(n)},T_{k-1}^{(n+1)})$$
for some function $f$ and initial values $T_0^{(n)}$. The "triangular" adjective is easily visualized if the $T_k^{(n)}$ are arranged in an array:
$$\begin{array}{}T_0^{(0)}&T_1^{(0)}&T_2^{(0)}&T_3^{(0)}\\T_0^{(1)}&T_1^{(1)}&T_2^{(1)}&\\T_0^{(2)}&T_1^{(2)}&&\\T_0^{(3)}&&&\end{array}$$
Due to the nice theory behind triangular recursions, it is known that such algorithms can be implemented using only a one-dimensional scratch array instead of a two-dimensional array.
So much for theory. I shall now give various examples of triangular recursions that occur in computational practice, as implemented in Mathematica, with the underlying triangular recursion flanked by (* ------ *)
comment lines.
Here is the Akiyama-Tanigawa algorithm for the Bernoulli numbers, with the redefinition $B_1=\frac12$:
myBernoulliB[n_Integer] := Module[{atArray = 1/Range[n + 1]},
(* ------ *)
Do[
atArray[[j]] = j (atArray[[j]] - atArray[[j + 1]]),
{k, n + 1}, {j, k - 1, 1, -1}];
(* ------ *)
First[atArray]]
myBernoulliB /@ Range[0, 20]
{1, 1/2, 1/6, 0, -(1/30), 0, 1/42, 0, -(1/30), 0, 5/66}
BernoulliB[Range[0, 20]]
{1, -(1/2), 1/6, 0, -(1/30), 0, 1/42, 0, -(1/30), 0, 5/66}
Here's the Neville-Aitken algorithm for polynomial interpolation:
iPolyVal[pts_?MatrixQ, x_] := Module[{n = Length[pts] - 1, xa, ya, temp},
{xa, ya} = Transpose[pts];
(* ------ *)
Do[
temp = (x - xa[[j]])/(x - xa[[k]]) - 1;
ya[[j]] = ya[[j + 1]] + (ya[[j + 1]] - ya[[j]])/temp,
{k, n + 1}, {j, k - 1, 1, -1}];
(* ------ *)
First[ya]]
iPolyVal[{{0, 1}, {1/3, 1}, {1/2, 1/2}, {3/4, -1}, {1, 0}}, 1/4]
161/160
InterpolatingPolynomial[{{0, 1}, {1/3, 1}, {1/2, 1/2}, {3/4, -1}, {1, 0}}, 1/4]
161/160
Here's (a simplified version of) de Casteljau's algorithm for splitting Bézier curves:
bezierChop[BezierCurve[pts_?MatrixQ, opts___], u_?NumericQ] :=
Module[{n = Length[pts]},
BezierCurve[Transpose[
Function[{vec}, Block[{ta = vec},
(* ------ *)
Do[
ta[[j]] = u ta[[j + 1]] + (1 - u) ta[[j]],
{k, n - 1}, {j, k, 1, -1}];
(* ------ *)
ta]] /@ Transpose[pts]]]] /; 0 < u < 1
Graphics3D[{AbsoluteThickness[5],
BezierCurve[{{0, 0, 0}, {1, 1, 1}, {2, -1, 1}, {3, 0, 2}}],
Directive[Blue, AbsoluteThickness[3]],
Translate[
bezierChop[
BezierCurve[{{0, 0, 0}, {1, 1, 1}, {2, -1, 1}, {3, 0, 2}}],
1/3], {0, 0, 1/10}]}, Boxed -> False]
There are many more examples of situations that make use of triangular recursions, such as Romberg quadrature, divided differences for interpolating polynomials, the Levin transformation for summing series, the Cox-de Boor algorithm for B-splines... etc.
Having shown that they are important, note that the common core of the algorithms that use triangular recursions is a double-index Do[]
loop working on a preset scratch array, modifying elements as needed.
That should be sufficient preamble. My question now is: are there alternative methods (e.g. non-procedural methods) for implementing triangular recursions?