13

Memoization is a technique for improving performance by having a function remember its previous arguments. For example,

f[x_]:=f[x]=mySlowFunction[x]

will be slow on its first evaluation for a given x, but will be vastly faster on subsequent calls for that x, whereas repeated calls to mySlowFunction[x] will always be slow.

My case is that I would like to discretize the parameter space to increase performance at the expense of accuracy. Any Real value of x should be evaluated only at the rounded value, which itself should be memoized. Compare these toy examples:

Clear[f];
f[x_] := f[x] = Total[Table[x, {100000}]]

Clear[g];
g[x_Real] := g[Round[x]];
g[x_Integer] := g[x] = Total[Table[x, {100000}]]

Timing[f[0]][[1]]
  (* 0.002007 *)

Timing[f[0.1]][[1]]
  (* 0.001523 *)

Timing[g[0]][[1]]
  (* 0.002059 *)

Timing[g[0.1]][[1]]
  (* 0.000013 *)

The 2nd call to g[x] is much faster than the first call, even though the actual input (0.1) has not been memoized itself. The second call is less accurate, and I can live with that.

But I would like to Round[] to arbitrary precision. For example, I could try

g[x_Real] := g[Round[x,0.01]];

The g[x_Integer] pattern of the 2nd definition would fail in this case. I tried the naive

h[x_Real] := h[Round[x,0.01]] = Total[Table[x, {100000}]]

which does not produce the desired effect.

How could one memoize a function's inputs, such that subsequent calls to the function at values near the (rounded) memoized inputs are replaced by calls at those memoized inputs?

For the curious, I am tracing particles under the influence of a force. That force can be calculated to high precision at arbitrary locations, but this is slow. I'd like to cache the force at discrete locations, so that particles within some distance of a previously-calculated value use the cached value.

rcollyer
  • 33,976
  • 7
  • 92
  • 191
JxB
  • 5,111
  • 1
  • 23
  • 40
  • So this is multidimensional, i.e. you have f[x,y,z] and not just f[x], right? For the 1D case I had some easy ideas for good precision (interpolation), but the multidimensional one is more complex. – Szabolcs Jan 31 '12 at 23:27
  • It's a 2D case, but I'm interested in the 1D as well. Might be able to extend it. – JxB Jan 31 '12 at 23:29
  • 1
    BTW you can always use a pattern like f[x_ /; Round[x, prec] == x] := f[x] = slowf[x]; f[x_] := f[Round[x, prec]]. The variable prec gives you the precision. – Szabolcs Jan 31 '12 at 23:30
  • If this doesn't bear fruit, I'll bite the bullet, precompute all grid cell values, and use an Interpolation[]. For 2D maybe it's not all that bad, especially if I save the interpolatingfunction. – JxB Jan 31 '12 at 23:32
  • 1
    I'd go for the Interpolation[] approach – acl Jan 31 '12 at 23:58
  • @Szabolcs Your pattern works and my 2D case is much faster now! Go ahead and convert your comment to an answer if you like. :) – JxB Jan 31 '12 at 23:59
  • 1
    Memoization is a type of [tag:caching], so I removed the tag in favor of the more general one. – rcollyer Feb 01 '12 at 02:06

3 Answers3

16

This variant should do what you want.

g[x_Real] := h[Round[x,0.01]];
h[x_] := h[x] = Total[Table[x, {100000}]]

Your Interpolation idea might give better accuracy though, if the function inquestion is sufficiently well behaved on your grid.

Daniel Lichtblau
  • 58,970
  • 2
  • 101
  • 199
15

Since Dan's already taken my initial solution, here's another approach that additionally allows you to specify the precision:

f[x_, tol_] := f[Round[x, tol]]
f[x_] := f[x] = Total[Table[x, {100000}]]
Brett Champion
  • 20,779
  • 2
  • 64
  • 121
10

You can introduce a second symbol as Daniel shows, but I am stingy with symbols and prefer this:

g[x_?NumericQ] := g[{Round[x, 0.01]}];
m : g[{x_}] := m = Total[Table[x, {100000}]]

You could use any head for this, even a string:

g[x_?NumericQ] := g[ "rounded"[ Round[x, 0.01] ] ];
m : g["rounded"[x_]] := m = Total[Table[x, {100000}]]

Delving into the realm of overkill, you could even use Option syntax:

Options[g] = {Rounded -> False};
g[x_?NumericQ] := g[Round[x, 0.01], Rounded -> True];
m : g[x_, OptionsPattern[]] :=
  (m = Total[Table[x, {100000}]]) /; OptionValue[Rounded]
Mr.Wizard
  • 271,378
  • 34
  • 587
  • 1,371