Memoizing a function using only a subset of arguments as an identifier

Suppose I have a function of two arguments. For instance,

function foo(a::Float64,b::Float64)
   return a^2 - b
end

Is it possible to memoize it based on the first argument only? I understand how I can partially achieve this goal:

using Memoization

function foo(b::Float64)
   @memoize function f(a::Float64)
      return a^2 - b
   end
end

Now I can just define g1 = foo(1.0), g2 = foo(2.0), etc. But it seems there should be a more elegant way to get what I want, such that I don’t need to define a new function for every ‘dummy’ argument.

Also, just as a reference, here’s the stackoverflow thread that covers exactly the same question, albeit in Python.

P.S. I am well aware that what my foo returns depends on b, so it seems odd to memoize it this way, etc. This is just a stupid example that is not meant to be taken seriously.

I don’t really get the point… If your function output depends only on some inputs (and not further side-effects, which would make Memoization pointless anyways), then why not discard the unused arguments and make it a function of fewer arguments? This also applies to the stackoverflow thread you linked.

I feel like this is a X-Y question and the true problem sits elsewhere and should be fixed there. Can you perhaps provide a bit more context?

Yes, I’ve already thought that it might help to give some context.

I am solving a differential equation (well, an integro-differential equation) in which the right-hand side is a functional of the unknown variable itself. For simplicity, let’s say it’s your usual memory integral:

\dot{u}(t) = -u(t) - \int_0^t dt' u(t')\,.

Using DifferentialEquations.jl I can define the right-hand side as

function f!(du,u,p,t)
    du[1] = -u[1] - mint(t,p)
end

where mint(t::Float64,p::Vector{Float64}) is exactly the memory integral in question, with the second argument being the solution up until time t:

using Trapz

# dt is my timestep, dom is the time domain array

function mint(t,usol)
    return trapz(dom[1:Int(round(t/dt))],usol[1:Int(round(t/dt))])
end

Now, say I use a fixed-step integrator of order 3. Then, in order to evaluate my solution at time t, I would need the values of my memory integral at times t, t-1, and t-2. But the latter two should have been already computed by that point. So if I cached their values, I would not have to repeat the computation.

The problem is that I have to pass a new “solution array” at each timestep, so what will be called at time t are mint(t, sol at t), mint(t-1, sol at t), and mint(t-2, sol at t), whereas the computed values are mint(t-1, sol at t-1) and mint(t-2, sol at t-2). Obviously, mint(t-1, sol at t-1) = mint(t-1, sol at t), but I need to let the machine know it somehow.

Ah okay I see where you are coming from. While one certainly could write some memoization like you proposed, e.g.

function make_memory_kernel()
  let data = Dict{Float64, Float64}() # maybe choose more clever datastructure here
    function kernel(t, usol)
      get!(data, t) do
        mint(t, usol)
      end
    end
  end
end

Another idea would be rewriting your equations in first order by introducing auxiliary variables (like one usually transforms a N’th order ODE to N first order ODEs). In your example call v \equiv \int_0^t u(s) ds, then you will get the system:

\begin{align} \dot v(t) &= u(t)\\ \dot u(t) &= -u(t) - v(t) \end{align}

That way you don’t need to do any integration yourself and the integrator handles it all for you.