How can I implement dynamic programming for a function with more than one argument?
Yes, there is, although the speed-up is not as dramatic as for 1D memoization:
ClearAll[CharlierC];
CharlierC[0, a_, x_] := 1;
CharlierC[1, a_, x_] := x - a;
CharlierC[n_Integer, a_, x_] :=
Module[{al, xl},
Set @@ Hold[CharlierC[n, al_, xl_],
Expand[(xl - al - n + 1) CharlierC[n - 1, al, xl] -
al (n - 1) CharlierC[n - 2, al, xl]
]];
CharlierC[n, a, x]
];
(Thanks to @Mike Bantegui for pointing out the wastefulness of Simplify
, which has now been removed).
What you memoize here are function definitions.Expand
is used to not accumulate the complexity too fast. The idea is that I first create a new pattern-based definition, using a number of tricks to fool the scoping variable - renaming mechanism but localize pattern variables, and then evaluate this definition.
For example:
In[249]:= CharlierC[20,a,x];//Timing
Out[249]= {0.063,Null}
In[250]:= CharlierC[25,a,x];//Timing
Out[250]= {0.078,Null}
While with clear definitions:
In[260]:= CharlierC[25,a,x];//Timing
Out[260]= {0.094,Null}
Here are a first few generated definitions:
In[262]:= Take[DownValues[CharlierC],4]
Out[262]=
{HoldPattern[CharlierC[0,a_,x_]]:>1,
HoldPattern[CharlierC[1,a_,x_]]:>x-a,
HoldPattern[CharlierC[2,al$4106_,xl$4106_]]:>
al$4106^2-xl$4106-2 al$4106 xl$4106+xl$4106^2,
HoldPattern[CharlierC[3,al$4105_,xl$4105_]]:>
-al$4105^3+2 xl$4105+3 al$4105 xl$4105+3 al$4105^2 xl$4105
-3 xl$4105^2-3 al$4105 xl$4105^2+xl$4105^3}
Nice question. This is my suggested implementation. Evaluate all code at once.
Clear[CharlierC, "CharlierC`*"]
CharlierC (* create symbol in current context *)
Begin["CharlierC`"];
implementation[0] := 1;
implementation[1] := x - a;
implementation[n_Integer] :=
implementation[n] = Expand[(x - a - n + 1) implementation[n - 1] -
a (n - 1) implementation[n - 2] ]
CharlierC[n_Integer ? NonNegative, ai_, xi_] :=
Block[{a, x}, implementation[n] /. {a -> ai, x -> xi}]
End[];
The Block
isn't strictly necessary as a
and x
are already created in the CharlierC`
context where they should be safe.
This function memoizes the symbolic representation of CharlierC
only, and only for each n
(not a
and x
). Then substitutes in whatever variables or numbers you pass in.
I would also suggest to use pure functions here:
CharlierC[0] = 1 &;
CharlierC[1] = #2 - #1 &;
CharlierC[n_Integer] := (CharlierC[n] =
Evaluate[
Expand[(#2 - #1 - n + 1) CharlierC[n - 1][#1, #2] - #1 (n -
1) CharlierC[n - 2][#1, #2]]] &);
CharlierC[20][a, x] // AbsoluteTiming
(* ==> {0.0312414,
a^20 - 121645100408832000 x - 128047474114560000 a x -
67580611338240000 a^2 x - 23851980472320000 a^3 x -
6335682312960000 a^4 x - 1351612226764800 a^5 x -
241359326208000 a^6 x - 37132204032000 a^7 x -
5028319296000 a^8 x - 609493248000 a^9 x - 67044257280 a^10 x -
6772147200 a^11 x - 634888800 a^12 x - ...
*)
I haven't made comparisons, but this seems to be faster.