Taxol model
As an example of practical identifiability analysis, we use the Cancer Taxol Treatment Model. It is an ODE model with 3 state variables and 5 parameters. The identifiability of this model was studied in Marisa C.Eisenberg, Harsh V.Jain. A confidence building exercise in data and identifiability. We have translated author's MATLAB code into Julia. The model is defined by the following system of differential equations:
using LikelihoodProfiler, OptimizationLBFGSB, OrdinaryDiffEq, Distributions, Plots
# https://github.com/marisae/cancer-chemo-identifiability/blob/master/Profile%20Likelihood/testa0_de.m
function ode_func(du, u, p, t, drug)
let (a0, ka, r0, d0, kd) = (p[1], p[2], p[3], p[4], p[5])
K = 10.515*100
V0 = 1.3907*K
lam = 9.5722
theta = 10.
# Values taken from
aRP = 20. # per day from Kim_PrlifQuies
Ncel = u[1] + u[2] + u[3]
Lfac = ((K-Ncel)^theta)/((V0^theta) + ((K-Ncel)^theta))
arstexp = 3.
adthexp = 4.
arst = a0*(drug^arstexp)/(ka^arstexp + (drug^arstexp))
adth = d0*(drug^adthexp)/(kd^adthexp + (drug^adthexp))
arcv = r0
# The differntial equations
du[1] = -lam*u[1] + aRP*u[2]*Lfac - arst*u[1] + arcv*u[3]
du[2] = 2*lam*u[1] - aRP*u[2]*Lfac
du[3] = arst*u[1] - adth*u[3] - arcv*u[3]
end
endode_func (generic function with 1 method)Experimental datasets are also provided in the cancer-chemo-identifiability repo for four drug doses (5, 10, 40, 100)
# https://github.com/marisae/cancer-chemo-identifiability/blob/master/Profile%20Likelihood/testa0_fit.m
# Data from Terzis et al. Brit J Cancer 1997;75:1744.
# From Bowman et al. Glia 1999;27:22, glioma cell volume is 0.916
# picoliters, 1 mm^3 = 1e6 pl or ~1.091 million cells
times = [0., 3., 6., 9., 12., 15.] # days
dose = [5., 10., 40., 100.]; # dose in ng/ml
# Control data
Cell = [0.009, 0.050, 0.120, 0.189, 0.230, 0.260]*1091.0 # thousands of cells
Cerr = [0.006, 0.012, 0.010, 0.011, 0.011, 0.011]*1091.0 # thousands of cells
# 0.005 ug/ml Taxol
Cell005 = [0.009, 0.047, 0.089, 0.149, 0.198, 0.219]*1091.0 # thousands of cells
Cerr005 = [0.006, 0.013, 0.010, 0.011, 0.013, 0.010]*1091.0 # thousands of cells
# 0.010 ug/ml Taxol
Cell010 = [0.009, 0.043, 0.077, 0.093, 0.109, 0.128]*1091.0 # thousands of cells
Cerr010 = [0.006, 0.012, 0.013, 0.012, 0.014, 0.012]*1091.0 # thousands of cells
# 0.040 ug/ml Taxol
Cell040 = [0.009, 0.025, 0.047, 0.054, 0.076, 0.085]*1091.0 # thousands of cells
Cerr040 = [0.005, 0.010, 0.010, 0.011, 0.010, 0.010]*1091.0 # thousands of cells
# 0.100 ug/ml Taxol
Cell100 = [0.009, 0.025, 0.026, 0.028, 0.029, 0.031]*1091.0 # thousands of cells
Cerr100 = [0.006, 0.010, 0.009, 0.008, 0.011, 0.011]*1091.0 # thousands of cells
C005 = mean(Cell005)
C010 = mean(Cell010)
C040 = mean(Cell040)
C100 = mean(Cell100)
data = [Cell005/C005, Cell010/C010, Cell040/C040, Cell100/C100]
datamean = [C005, C010, C040, C100]4-element Vector{Float64}:
129.2835
83.4615
53.82266666666667
26.911333333333335Next we define solver options, initial values, optimal parameter values, and tspan
# solver algorithm and tolerances
solver_opts = Dict(
:alg => AutoTsit5(Rosenbrock23()),
:reltol => 1e-6,
:abstol => 1e-8
)
# initial values and parameters
# https://github.com/marisae/cancer-chemo-identifiability/blob/master/Profile%20Likelihood/testa0_soln.m#L3-L6
# https://github.com/marisae/cancer-chemo-identifiability/blob/master/Profile%20Likelihood/testa0_fit.m#L4
u0 = [7.2700, 2.5490, 0.]
p0 = [8.3170, 8.0959, 0.0582, 1.3307, 119.1363]
tspan = (0.,15.)(0.0, 15.0)We define an objective function (OLS) as in the original publication. The profile likelihood evaluates how this objective changes when one parameter is varied and all other parameters are re-optimized.
# https://github.com/marisae/cancer-chemo-identifiability/blob/master/Profile%20Likelihood/testa0_fit.m#L92
# https://www.mathworks.com/help/optim/ug/lsqcurvefit.html
function taxol_obj(x, _p)
loss = 0.
for (i,d) in enumerate(dose)
prob = ODEProblem((du,u,p,t)->ode_func(du,u,p,t,d), u0, tspan, x)
sol = solve(prob,
solver_opts[:alg],
reltol=solver_opts[:reltol],
abstol=solver_opts[:abstol],
saveat=times)
sim = (sol[1,:] + sol[2,:] + sol[3,:])/datamean[i]
loss += sum((sim-data[i]).^2)
end
return loss
endtaxol_obj (generic function with 1 method)We define the threshold as in the original publication. The threshold line represents the value of the objective function (or negative log-likelihood) that corresponds to the chosen confidence level. Where the profile curve intersects this threshold gives the lower and upper CI bounds.
# threshold is chosen according to
# https://github.com/marisae/cancer-chemo-identifiability/blob/master/Profile%20Likelihood/testa0_fit.m#L40-L41
sigmasq = (mean([(Cerr005/C005); (Cerr010/C010); (Cerr040/C040); (Cerr100/C100)]))^2
threshold = sigmasq*chi2_quantile(0.95, 5)0.4425378798475086Next, we construct the profile likelihood problem ProfileLikelihoodProblem for the five unknown parameters:
lb = [2.0, 2.0, 0.01, 0.05, 30.]
ub = [30.0, 30.0, 0.6, 5.0, 250.0]
optf = OptimizationFunction(taxol_obj, AutoForwardDiff())
optprob = OptimizationProblem(optf, p0; lb=lb, ub=ub)
plprob = ProfileLikelihoodProblem(optprob, p0; threshold)Profile Likelihood Problem. Profile threshold: 0.4425378798475086
ParameterTarget: 5 parameter(s) to profile.
Parameters' optimal values:
5-element Vector{Float64}:
8.317
8.0959
0.0582
1.3307
119.1363Here we select one of the profiling methods: OptimizationProfiler with the FixedStep stepping algorithm (see Profile Likelihood Methods for details). For each profiled parameter, this algorithm takes fixed-size steps to the left and right of the optimal parameter value and reoptimizes all remaining parameters using the chosen optimizer to obtain the next point on the profile curve.FixedStep uses the same step size (initial_step) for all steps of a given parameter, but allows different parameters to have different step sizes. In this example, we set the initial step to 10% of the parameter’s optimal value, providing a reasonable scale for exploring the likelihood curve.
profile_step(p0, i) = p0[i] * 0.1
method = OptimizationProfiler(optimizer = LBFGSB(), stepper = FixedStep(; initial_step=profile_step))
sol = solve(plprob, method)Profile Likelihood solution. Use `sol[i]` to index the computed profiles.
ProfileLikelihoodSolution stores the computed profile curves together with confidence interval endpoints and identification retcodes, which indicate whether each parameter is practically identifiable. These values can be accessed directly:
retcodes(sol)
endpoints(sol)5-element Vector{NamedTuple{(:left, :right)}}:
(left = 6.66270373265725, right = 17.354392670202092)
(left = 4.932838242318651, right = 10.749448225222089)
(left = nothing, right = 0.4053690198705072)
(left = 0.20273235691483638, right = nothing)
(left = 50.00767957334199, right = nothing)Finally, we plot the resulting profiles. Each point on the curve corresponds to a profiler step, i.e., a constrained optimization performed at a fixed value of the profiled parameter. The horizontal line indicates the likelihood threshold for the chosen confidence level; its intersections with the curve define the confidence interval bounds. Steeper profiles indicate better identifiability, whereas flat curves or curves that never intersect the threshold indicate parameters that are not practically identifiable.
plot(sol, size=(800,300), margins=5Plots.mm)