Before I open a bug report on the official repo, just want to make sure I am on the right path. Consider a simple 2 parameter estimation of an ODE.
using DifferentialEquations
using DiffEqParamEstim
using Optim
# some data for reproducibility
data = [3140, 3232, 3488, 3415, 3192, 3265, 2961, 3548, 4269, 4681, 4415, 4883, 4150, 4394, 5856, 7126, 9130, 9367, 10631, 10737, 11688, 14993, 20665, 23957, 23621, 21338, 22034, 33781, 32013, 39840, 45961, 45359, 46126, 38077, 37410, 39431]
# growth model
function f(C,params,t)
r, p = params[1], params[2]
return r*C^p
end
c0 = 3140n. # initial condition
tspan = (0,length(data)-1) # time span
init_params = [0.5, 0.8] # initial parameters
prob = ODEProblem(f, c0, tspan, init_params) #sets up the problem
sol = solve(prob, Tsit5(), reltol=1e-8, abstol=1e-8, saveat=1) # get the solution to plot, etc
# lets try to optimize the parameters r and p
cost_function = build_loss_objective(prob, Tsit5(), L2Loss(tspan, data), maxiters=10000,verbose=false)
cost_function([0.5, 0.2]) # evaluate the cost of these two specific parameter values
The return of build_loss_objective
is a function (i.e., cost_function
) can be evaluated for different values of parameters r
and p
, i.e., cost_function([r, p])
which gives you a numeric value. This makes sense.
Now lets use LeastSquaresOptim.jl
to solve the optimization using least squares. We will build our cost_function
using the provided build_lsoptim_objective(prob, t, data, alg)
which is slightly different than build_loss_objective
. We have:
cost_function = build_lsoptim_objective(prob, 1, data, Tsit5())
lsp = LeastSquaresProblem(x = [0.5, 0.5], f! = cost_function, output_length = 36)
res = optimize!(lsp, LeastSquaresOptim.Dogleg())
and here is where I found the inconsistency. I had to read the source code in order to figure out the inconsistency. Let me explain:
-
The
t
argument in thebuild_lsoptim_objective
function sounds like it acceptstspan
, i.e., the time span for the ODE problem. This is not the case. Line 10 of the source code suggests thet
argument is used only for thesaveat
parameter in the solver. This is why my cost function has that integer1
. -
build_lsoptim_objective
returns a function, just likebuild_loss_objective
above. Except, it has the signature
function (out, p)
...
end
where out
is a pre-allocated vector to store the results of the ODE in for a given parameter p
. This is where the inconsistency is. However, like in the above example, you would expect that cost_function
would accept an array of parameters and simply evaluate the cost. That is, cost_function([r, p]
should produce a value for for any two parameters r
and p
… but instead the correct call is cost_function(zeros(length_data), [r, p]
).
- The annoying part here then becomes the argument
output_length
in theLeastSquaresProblem
constructor. For a few hours, I thought theoutput_length
referred to the number of parameters that one is trying to optimize, but infact this is the number of data points in the solution of the ODE.
I hope all that made sense… Kind of seems long. Maybe there is a better way to explain.