tests/test.sh exit code (0 → resolved); the classification below is post-hoc and cannot change it.ls /app/ && echo "---" && cat /app/dataset_manifest.json && echo "---" && head -30 /app/Phenobarb.csv
Phenobarb.csv
analysis.jl
dataset_manifest.json
output
---
{
"dataset_name": "Phenobarb",
"source": "nlme::Phenobarb (Pinheiro & Bates 2000, Mixed-Effects Models in S and S-PLUS, section 6.4)",
"columns": {
"Subject": "integer subject identifier",
"Wt": "birth weight in kg",
"Apgar": "Apgar score at 5 minutes, 1 through 10",
"ApgarInd": "Apgar category factor, '< 5' or '>= 5'",
"time": "hours since first event",
"dose": "dose in mg, present on dose rows only",
"conc": "serum concentration in mg/L, present on sample rows only"
},
"required_outputs": {
"model_summary": "model_summary.json",
"lrt_chain": "lrt_chain.csv",
"per_subject_params": "per_subject_params.csv",
"obs_vs_pred": "obs_vs_pred.csv",
"residual_diagnostics": "residual_diagnostics.csv",
"plot_conc_profile": "plot_conc_profile.png",
"plot_per_subject_fit": "plot_per_subject_fit.png",
"plot_covariate_effect": "plot_covariate_effect.png",
"plot_obs_vs_pred": "plot_obs_vs_pred.png",
"plot_residuals": "plot_residuals.png"
},
"column_order": {
"lrt_chain": ["step", "model_name", "n_params", "logLik", "AIC", "BIC", "LR_stat", "LR_pvalue"],
"per_subject_params": ["Subject", "Cl", "V"],
"obs_vs_pred": ["Subject", "time", "conc_obs", "pred_pop", "pred_ind"],
"residual_diagnostics": ["Subject", "time", "fitted_ind", "resid", "std_resid"]
},
"model_chain": {
"step_1": "base: lCl + lV ~ 1",
"step_2": "cl_wt: lCl ~ Wt, lV ~ 1",
"step_3": "full: lCl ~ Wt + ApgarInd, lV ~ Wt"
}
}
---
"Subject","Wt","Apgar","ApgarInd","time","dose","conc"
1,1.4,7,">= 5",0,25,
1,1.4,7,">= 5",2,,17.3
1,1.4,7,">= 5",12.5,3.5,
1,1.4,7,">= 5",24.5,3.5,
1,1.4,7,">= 5",37,3.5,
1,1.4,7,">= 5",48,3.5,
1,1.4,7,">= 5",60.5,3.5,
1,1.4,7,">= 5",72.5,3.5,
1,1.4,7,">= 5",85.3,3.5,
1,1.4,7,">= 5",96.5,3.5,
1,1.4,7,">= 5",108.5,3.5,
1,1.4,7,">= 5",112.5,,31
2,1.5,9,">= 5",0,15,
2,1.5,9,">= 5",2,,9.7
2,1.5,9,">= 5",4,3.8,
2,1.5,9,">= 5",16,3.8,
2,1.5,9,">= 5",27.8,3.8,
2,1.5,9,">= 5",40,3.8,
2,1.5,9,">= 5",52,3.8,
2,1.5,9,">= 5",63.5,,24.6
2,1.5,9,">= 5",64,3.8,
2,1.5,9,">= 5",76,3.8,
2,1.5,9,">= 5",88,3.8,
2,1.5,9,">= 5",100,3.8,
2,1.5,9,">= 5",112,3.8,
2,1.5,9,">= 5",124,3.8,
2,1.5,9,">= 5",135.5,,33
3,1.5,6,">= 5",0,30,
3,1.5,6,">= 5",1.5,,18wc -l /app/Phenobarb.csv && awk -F',' 'NR>1 {print $1}' /app/Phenobarb.csv | sort -u | wc -l745 /app/Phenobarb.csv 59
cat /app/analysis.jl 2>/dev/null; echo "---"; ls /app/output/ 2>/dev/null
# Work only in this file.
#
# Inputs:
# /app/Phenobarb.csv , neonatal phenobarbital PK data
# /app/dataset_manifest.json , output filename and column contracts
#
# Outputs: write everything to /app/output/
using DataFrames, CSV, JSON3, CairoMakie, Statistics, Distributions,
LinearAlgebra, Optim
data_path() = get(ENV, "PHENOBARB_DATA_PATH", "/app/Phenobarb.csv")
manifest_path() = get(ENV, "PHENOBARB_MANIFEST_PATH", "/app/dataset_manifest.json")
output_dir() = get(ENV, "PHENOBARB_OUTPUT_DIR", "/app/output")
function main()
out_dir = output_dir()
mkpath(out_dir)
# TODO: implement the full three-step NLME covariate chain on the
# neonatal phenobarbital dataset, write the five output tables listed
# in the manifest, and render the five diagnostic plots.
error("analysis.jl is not implemented yet")
end
main()---awk -F',' 'NR>1 {print $1, $2, $4}' /app/Phenobarb.csv | sort -u | head -10
echo "---"
awk -F',' 'NR>1 && $7!="" {n++} END {print "N conc obs:", n}' /app/Phenobarb.csv
awk -F',' 'NR>1 && $6!="" {n++} END {print "N dose rows:", n}' /app/Phenobarb.csv
awk -F',' 'NR>1 {print $4}' /app/Phenobarb.csv | sort | uniq -c1 1.4 ">= 5"
10 1.4 ">= 5"
11 1.2 ">= 5"
12 1.3 ">= 5"
13 1.1 ">= 5"
14 1.1 ">= 5"
15 1.3 ">= 5"
16 1.2 ">= 5"
17 1.1 ">= 5"
18 1 ">= 5"
---
N conc obs: 155
N dose rows: 589
109 "< 5"
635 ">= 5"command: create
path: /app/analysis.jl
file_text: # Work only in this file.
#
# Inputs:
# /app/Phenobarb.csv , neonatal phenobarbital PK data
# /app/dataset_manifest.json , output filename and column contracts
#
# Outputs: write everything to /app/output/
using DataFrames, CSV, JSON3, CairoMakie, Statistics, Distributions,
LinearAlgebra, Optim
data_path() = get(ENV, "PHENOBARB_DATA_PATH", "/app/Phenobarb.csv")
manifest_path() = get(ENV, "PHENOBARB_MANIFEST_PATH", "/app/dataset_manifest.json")
output_dir() = get(ENV, "PHENOBARB_OUTPUT_DIR", "/app/output")
# -------- Data structures --------
struct SubjData
id::Int
Wt::Float64
ApgarInd::String
apgar_hi::Float64 # 1 if ">= 5" else 0
dose_t::Vector{Float64}
dose_amt::Vector{Float64}
samp_t::Vector{Float64}
samp_y::Vector{Float64}
end
function build_subjects(df::DataFrame)
subs = SubjData[]
for sid in sort(unique(df.Subject))
sdf = df[df.Subject .== sid, :]
wt = Float64(sdf.Wt[1])
apg = string(sdf.ApgarInd[1])
apg_dummy = apg == ">= 5" ? 1.0 : 0.0
dt = Float64[]; da = Float64[]
st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
if !ismissing(row.dose)
push!(dt, Float64(row.time)); push!(da, Float64(row.dose))
end
if !ismissing(row.conc)
push!(st, Float64(row.time)); push!(sy, Float64(row.conc))
end
end
push!(subs, SubjData(sid, wt, apg, apg_dummy, dt, da, st, sy))
end
return subs
end
# -------- PK prediction --------
@inline function predict_conc(t::Float64, Cl::Float64, V::Float64,
dose_t::Vector{Float64}, dose_amt::Vector{Float64})
ke = Cl / V
c = 0.0
@inbounds for i in eachindex(dose_t)
td = dose_t[i]
if td <= t
c += dose_amt[i] / V * exp(-ke * (t - td))
end
end
return c
end
# -------- Parameter unpacking --------
struct PKParams
cl_int::Float64
cl_wt::Float64
cl_apgar::Float64
v_int::Float64
v_wt::Float64
σCl::Float64
σV::Float64
σ::Float64
end
function unpack_params(model::Symbol, θ::AbstractVector)
if model == :base
return PKParams(θ[1], 0.0, 0.0, θ[2], 0.0,
exp(θ[3]), exp(θ[4]), exp(θ[5]))
elseif model == :cl_wt
return PKParams(θ[1], θ[2], 0.0, θ[3], 0.0,
exp(θ[4]), exp(θ[5]), exp(θ[6]))
elseif model == :full
return PKParams(θ[1], θ[2], θ[3], θ[4], θ[5],
exp(θ[6]), exp(θ[7]), exp(θ[8]))
else
error("unknown model $model")
end
end
npar_total(model::Symbol) =
model == :base ? 5 : model == :cl_wt ? 6 : 8
nfixed(model::Symbol) =
model == :base ? 2 : model == :cl_wt ? 3 : 5
# -------- Inner negative joint log-lik --------
@inline function compute_lCl_lV(p::PKParams, s::SubjData)
lCl = p.cl_int + p.cl_wt * s.Wt + p.cl_apgar * s.apgar_hi
lV = p.v_int + p.v_wt * s.Wt
return lCl, lV
end
function neg_joint_loglik(η::AbstractVector, s::SubjData, p::PKParams)
lCl, lV = compute_lCl_lV(p, s)
lCl += η[1]; lV += η[2]
Cl = exp(lCl); V = exp(lV)
nll = 0.0
invσ² = 1.0 / (p.σ * p.σ)
@inbounds for i in eachindex(s.samp_t)
pred = predict_conc(s.samp_t[i], Cl, V, s.dose_t, s.dose_amt)
r = s.samp_y[i] - pred
nll += 0.5 * r * r * invσ²
end
n_samp = length(s.samp_t)
nll += n_samp * (log(p.σ) + 0.5 * log(2π))
# priors on η
nll += 0.5 * (η[1] / p.σCl)^2 + log(p.σCl) + 0.5 * log(2π)
nll += 0.5 * (η[2] / p.σV)^2 + log(p.σV) + 0.5 * log(2π)
return nll
end
# -------- Laplace for one subject --------
function laplace_subject(s::SubjData, p::PKParams;
η_init::Vector{Float64}=[0.0, 0.0])
f(η) = neg_joint_loglik(η, s, p)
res = try
optimize(f, η_init, BFGS(), Optim.Options(
…[truncated]Existing file was successfully overwritten at /app/analysis.jl
cd /app && timeout 290 julia --project=. analysis.jl 2>&1 | tail -100
[stderr] Terminated
cd /app && cat > /tmp/time_test.jl <<'EOF'
include("analysis.jl")
EOF
echo "Running basic speed check..."
cd /app && timeout 40 julia -e '
using DataFrames, CSV
include("analysis.jl")' 2>&1 | head -20Running basic speed check... [stderr] Terminated
cd /app && cat > /tmp/profile.jl <<'EOF'
using DataFrames, CSV, Distributions, Optim, LinearAlgebra
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
# Minimal subject struct
struct S
doses_t::Vector{Float64}
doses_amt::Vector{Float64}
samp_t::Vector{Float64}
samp_y::Vector{Float64}
Wt::Float64
apgar_hi::Float64
end
subs = S[]
for sid in sort(unique(df.Subject))
sdf = df[df.Subject .== sid, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
if !ismissing(row.dose); push!(dt, row.time); push!(da, row.dose); end
if !ismissing(row.conc); push!(st, row.time); push!(sy, row.conc); end
end
ah = sdf.ApgarInd[1] == ">= 5" ? 1.0 : 0.0
push!(subs, S(dt, da, st, sy, sdf.Wt[1], ah))
end
println("n subj: ", length(subs))
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V
c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
function nll_inner(η, s, cl_int, cl_wt, cl_apg, v_int, v_wt, σCl, σV, σ)
lCl = cl_int + cl_wt*s.Wt + cl_apg*s.apgar_hi + η[1]
lV = v_int + v_wt*s.Wt + η[2]
Cl = exp(lCl); V = exp(lV)
nll = 0.0
for i in eachindex(s.samp_t)
pred = predc(s.samp_t[i], Cl, V, s.doses_t, s.doses_amt)
r = s.samp_y[i] - pred
nll += 0.5*(r/σ)^2
end
n = length(s.samp_t)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η[1]/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η[2]/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
# Test one call
s = subs[1]
println("One call: ", @elapsed nll_inner([0.0, 0.0], s, -4.6, 0.0, 0.0, 0.0, 0.0, 0.3, 0.3, 2.0))
println("One call (warm): ", @elapsed nll_inner([0.0, 0.0], s, -4.6, 0.0, 0.0, 0.0, 0.0, 0.3, 0.3, 2.0))
# Time a single BFGS inner solve
function solve_inner(s, cl_int, v_int, σCl, σV, σ; η0=[0.0, 0.0])
f(η) = nll_inner(η, s, cl_int, 0.0, 0.0, v_int, 0.0, σCl, σV, σ)
res = optimize(f, η0, BFGS(), Optim.Options(g_tol=1e-5, iterations=50))
return Optim.minimizer(res), Optim.minimum(res)
end
# Warmup
solve_inner(s, -4.6, 0.0, 0.3, 0.3, 2.0)
t = @elapsed for _ in 1:100; solve_inner(s, -4.6, 0.0, 0.3, 0.3, 2.0); end
println("Inner solve x100: $t s → ", t/100*1e3, " ms each")
t = @elapsed for ss in subs; solve_inner(ss, -4.6, 0.0, 0.3, 0.3, 2.0); end
println("All $(length(subs)) subjects inner solve: $t s")
EOF
cd /app && timeout 60 julia --project=. /tmp/profile.jl 2>&1 | tail -20n subj: 59 One call: 0.036919756 One call (warm): 5.606e-6 Inner solve x100: 0.002069785 s → 0.02069785 ms each All 59 subjects inner solve: 0.015027555 s
cd /app && cat >> /tmp/profile.jl <<'EOF'
# Time a full marginal likelihood evaluation
function full_marginal(cl_int, v_int, σCl, σV, σ, subs)
total = 0.0
for s in subs
f(η) = nll_inner(η, s, cl_int, 0.0, 0.0, v_int, 0.0, σCl, σV, σ)
res = optimize(f, [0.0, 0.0], BFGS(), Optim.Options(g_tol=1e-6, iterations=50))
η̂ = Optim.minimizer(res); nll_min = Optim.minimum(res)
# Hessian
ε = 1e-4
f00 = nll_min
H11 = (f(η̂ + [ε,0]) - 2f00 + f(η̂ - [ε,0])) / (ε*ε)
H22 = (f(η̂ + [0,ε]) - 2f00 + f(η̂ - [0,ε])) / (ε*ε)
H12 = (f(η̂ + [ε,ε]) - f(η̂ + [ε,-ε]) - f(η̂ + [-ε,ε]) + f(η̂ + [-ε,-ε])) / (4ε*ε)
detH = H11*H22 - H12^2
logL_marg = -nll_min + log(2π) - 0.5*log(detH)
total -= logL_marg
end
total
end
println("Full marginal (warmup): ", @elapsed full_marginal(-4.6, 0.0, 0.3, 0.3, 2.0, subs))
t = @elapsed for _ in 1:10; full_marginal(-4.6, 0.0, 0.3, 0.3, 2.0, subs); end
println("Full marginal x10: $t s → ", t/10*1e3, " ms each")
# Try a BFGS outer
obj5(θ) = full_marginal(θ[1], θ[2], exp(θ[3]), exp(θ[4]), exp(θ[5]), subs)
println("Base fit: ")
t = @elapsed begin
res = optimize(obj5, [-4.6, 0.0, log(0.3), log(0.3), log(2.0)], BFGS(),
Optim.Options(g_tol=1e-3, iterations=100, show_trace=false))
end
println(" elapsed: $t s, iters=", Optim.iterations(res), ", f_calls=", Optim.f_calls(res), ", min=", Optim.minimum(res))
println(" θ̂ = ", Optim.minimizer(res))
EOF
cd /app && timeout 120 julia --project=. /tmp/profile.jl 2>&1 | tail -15[17] update_state!(d::OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}, state::Optim.BFGSState{Vector{Float64}, Matrix{Float64}, Float64, Vector{Float64}}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat})
@ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/solvers/first_order/bfgs.jl:163
[18] optimize(d::OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}, initial_x::Vector{Float64}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat}, options::Optim.Options{Float64, Nothing}, state::Optim.BFGSState{Vector{Float64}, Matrix{Float64}, Float64, Vector{Float64}})
@ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:88
[19] optimize
@ /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:54 [inlined]
[20] optimize(f::Function, x0::Vector{Float64}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat}, options::Optim.Options{Float64, Nothing}; inplace::Bool, autodiff::ADTypes.AutoFiniteDiff{Val{:central}, Val{:central}, Val{:hcentral}, Nothing, Nothing, Bool})
@ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/interface.jl:225
[21] optimize
@ /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/interface.jl:216 [inlined]
[22] macro expansion
@ /tmp/profile.jl:105 [inlined]
[23] top-level scope
@ ./timing.jl:395
in expression starting at /tmp/profile.jl:104cd /app && timeout 120 julia --project=. /tmp/profile.jl 2>&1 | grep -E "Full|Inner|subj|Base" | head -20
n subj: 59
Inner solve x100: 0.002418276 s → 0.024182759999999998 ms each
All 59 subjects inner solve: 0.014856249 s
Full marginal (warmup): 0.149888104
Full marginal x10: 0.020572264 s → 2.0572264 ms each
Base fit:
@ Base.Math ./math.jl:33
@ Base.Math ./special/log.jl:301
[7] (::NLSolversBase.var"#18#20"{typeof(obj5), DifferentiationInterfaceFiniteDiffExt.FiniteDiffGradientPrep{Tuple{typeof(obj5), ADTypes.AutoFiniteDiff{Val{:central}, Val{:central}, Val{:hcentral}, Nothing, Nothing, Bool}, Vector{Float64}, Tuple{}}, FiniteDiff.GradientCache{Nothing, Nothing, Nothing, Vector{Float64}, Val{:central}(), Float64, Val{true}()}, Float64, Float64, Bool}, ADTypes.AutoFiniteDiff{Val{:central}, Val{:central}, Val{:hcentral}, Nothing, Nothing, Bool}})(_g::Vector{Float64}, _x::Vector{Float64})
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/objective_types/oncedifferentiable.jl:67
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/interface.jl:139
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/interface.jl:128
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/interface.jl:246
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/interface.jl:220
[14] (::LineSearches.HagerZhang{Float64, Base.RefValue{Bool}})(ϕ::Function, ϕdϕ::LineSearches.var"#ϕdϕ#2"{Optim.ManifoldObjective{Flat, OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}}, Vector{Float64}, Vector{Float64}, Vector{Float64}}, c::Float64, phi_0::Float64, dphi_0::Float64)
[16] perform_linesearch!(state::Optim.BFGSState{Vector{Float64}, Matrix{Float64}, Float64, Vector{Float64}}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat}, d::Optim.ManifoldObjective{Flat, OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}})
[17] update_state!(d::OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}, state::Optim.BFGSState{Vector{Float64}, Matrix{Float64}, Float64, Vector{Float64}}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat})
[18] optimize(d::OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}, initial_x::Vector{Float64}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat}, options::Optim.Options{Float64, Nothing}, state::Optim.BFGSState{Vector{Float64}, Matrix{Float64}, Float64, Vector{Float64}})
[20] optimize(f::Function, x0::Vector{Float64}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat}, options::Optim.Options{Float64, Nothing}; inplace::Bool, autodiff::ADTypes.AutoFiniteDiff{Val{:central}, Val{:central}, Val{:hcentral}, Nothing, Nothing, Bool})cd /app && cat >> /tmp/profile.jl <<'EOF'
# Robust version: protect Hessian
function full_marginal_robust(cl_int, v_int, σCl, σV, σ, subs; cache=Dict{Int,Vector{Float64}}())
total = 0.0
for (si, s) in enumerate(subs)
η0 = get(cache, si, [0.0, 0.0])
f(η) = nll_inner(η, s, cl_int, 0.0, 0.0, v_int, 0.0, σCl, σV, σ)
res = optimize(f, η0, BFGS(), Optim.Options(g_tol=1e-6, iterations=50))
η̂ = Optim.minimizer(res); nll_min = Optim.minimum(res)
cache[si] = η̂
# Hessian
ε = 1e-4
f00 = nll_min
H11 = (f(η̂ + [ε,0]) - 2f00 + f(η̂ - [ε,0])) / (ε*ε)
H22 = (f(η̂ + [0,ε]) - 2f00 + f(η̂ - [0,ε])) / (ε*ε)
H12 = (f(η̂ + [ε,ε]) - f(η̂ + [ε,-ε]) - f(η̂ + [-ε,ε]) + f(η̂ + [-ε,-ε])) / (4ε*ε)
# Regularise to PD
tr = H11 + H22; det0 = H11*H22 - H12^2
# eigenvalues via discriminant
disc = sqrt(max(tr*tr - 4*det0, 0.0))
λmin = (tr - disc) / 2
if λmin <= 1e-6
shift = 1e-6 - λmin
H11 += shift; H22 += shift
det0 = H11*H22 - H12^2
end
logL_marg = -nll_min + log(2π) - 0.5*log(det0)
total -= logL_marg
end
total
end
# Base fit with cache warm start
cache = Dict{Int,Vector{Float64}}()
obj5b(θ) = full_marginal_robust(θ[1], θ[2], exp(θ[3]), exp(θ[4]), exp(θ[5]), subs; cache=cache)
obj5b([-4.6, 0.0, log(0.3), log(0.3), log(2.0)]) # warmup
println("Base fit (robust): ")
t = @elapsed begin
res = optimize(obj5b, [-4.6, 0.0, log(0.3), log(0.3), log(2.0)], BFGS(),
Optim.Options(g_tol=1e-3, iterations=200, show_trace=false))
end
println(" elapsed: $t s, iters=", Optim.iterations(res), ", f_calls=", Optim.f_calls(res), ", min=", Optim.minimum(res))
println(" θ̂ = ", Optim.minimizer(res))
EOF
cd /app && timeout 120 julia --project=. /tmp/profile.jl 2>&1 | grep -E "Full|Inner|subj|Base|elapsed|θ̂|min|conv" | head -20n subj: 59
Inner solve x100: 0.002554373 s → 0.02554373 ms each
All 59 subjects inner solve: 0.01627901 s
Full marginal (warmup): 0.143995631
Full marginal x10: 0.020137329 s → 2.0137329 ms each
Base fit:
@ Base.Math ./math.jl:33
@ Base.Math ./special/log.jl:301
[7] (::NLSolversBase.var"#18#20"{typeof(obj5), DifferentiationInterfaceFiniteDiffExt.FiniteDiffGradientPrep{Tuple{typeof(obj5), ADTypes.AutoFiniteDiff{Val{:central}, Val{:central}, Val{:hcentral}, Nothing, Nothing, Bool}, Vector{Float64}, Tuple{}}, FiniteDiff.GradientCache{Nothing, Nothing, Nothing, Vector{Float64}, Val{:central}(), Float64, Val{true}()}, Float64, Float64, Bool}, ADTypes.AutoFiniteDiff{Val{:central}, Val{:central}, Val{:hcentral}, Nothing, Nothing, Bool}})(_g::Vector{Float64}, _x::Vector{Float64})
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/objective_types/oncedifferentiable.jl:67
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/interface.jl:139
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/interface.jl:128
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/interface.jl:246
@ NLSolversBase /usr/local/share/julia/packages/NLSolversBase/gOqwS/src/interface.jl:220
[14] (::LineSearches.HagerZhang{Float64, Base.RefValue{Bool}})(ϕ::Function, ϕdϕ::LineSearches.var"#ϕdϕ#2"{Optim.ManifoldObjective{Flat, OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}}, Vector{Float64}, Vector{Float64}, Vector{Float64}}, c::Float64, phi_0::Float64, dphi_0::Float64)
[16] perform_linesearch!(state::Optim.BFGSState{Vector{Float64}, Matrix{Float64}, Float64, Vector{Float64}}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat}, d::Optim.ManifoldObjective{Flat, OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}})
[17] update_state!(d::OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}, state::Optim.BFGSState{Vector{Float64}, Matrix{Float64}, Float64, Vector{Float64}}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat})
[18] optimize(d::OnceDifferentiable{Float64, Vector{Float64}, Float64, Vector{Float64}}, initial_x::Vector{Float64}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat}, options::Optim.Options{Float64, Nothing}, state::Optim.BFGSState{Vector{Float64}, Matrix{Float64}, Float64, Vector{Float64}})
[20] optimize(f::Function, x0::Vector{Float64}, method::BFGS{LineSearches.InitialStatic{Float64}, LineSearches.HagerZhang{Float64, Base.RefValue{Bool}}, Nothing, Nothing, Flat}, options::Optim.Options{Float64, Nothing}; inplace::Bool, autodiff::ADTypes.AutoFiniteDiff{Val{:central}, Val{:central}, Val{:hcentral}, Nothing, Nothing, Bool})
@ ./timing.jl:395cd /app && cat > /tmp/profile2.jl <<'EOF'
using DataFrames, CSV, Distributions, Optim, LinearAlgebra
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
struct S
doses_t::Vector{Float64}; doses_amt::Vector{Float64}
samp_t::Vector{Float64}; samp_y::Vector{Float64}
Wt::Float64; apgar_hi::Float64
end
subs = S[]
for sid in sort(unique(df.Subject))
sdf = df[df.Subject .== sid, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
ah = sdf.ApgarInd[1] == ">= 5" ? 1.0 : 0.0
push!(subs, S(dt, da, st, sy, sdf.Wt[1], ah))
end
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
function nll_inner(η, s, ci, cw, cap, vi, vw, σCl, σV, σ)
lCl = ci + cw*s.Wt + cap*s.apgar_hi + η[1]
lV = vi + vw*s.Wt + η[2]
Cl = exp(lCl); V = exp(lV); nll = 0.0
for i in eachindex(s.samp_t)
r = s.samp_y[i] - predc(s.samp_t[i], Cl, V, s.doses_t, s.doses_amt)
nll += 0.5*(r/σ)^2
end
n = length(s.samp_t)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η[1]/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η[2]/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
function neg_marg(θ, model, subs, cache)
if model == :base
ci, cw, cap = θ[1], 0.0, 0.0
vi, vw = θ[2], 0.0
σCl, σV, σ = exp(θ[3]), exp(θ[4]), exp(θ[5])
elseif model == :cl_wt
ci, cw, cap = θ[1], θ[2], 0.0
vi, vw = θ[3], 0.0
σCl, σV, σ = exp(θ[4]), exp(θ[5]), exp(θ[6])
else
ci, cw, cap = θ[1], θ[2], θ[3]
vi, vw = θ[4], θ[5]
σCl, σV, σ = exp(θ[6]), exp(θ[7]), exp(θ[8])
end
total = 0.0
for (si, s) in enumerate(subs)
η0 = get(cache, si, [0.0, 0.0])
f(η) = nll_inner(η, s, ci, cw, cap, vi, vw, σCl, σV, σ)
res = optimize(f, η0, BFGS(), Optim.Options(g_tol=1e-6, iterations=50))
η̂ = Optim.minimizer(res); nll_min = Optim.minimum(res)
cache[si] = η̂
ε = 1e-4; f00 = nll_min
H11 = (f(η̂ + [ε,0]) - 2f00 + f(η̂ - [ε,0])) / (ε*ε)
H22 = (f(η̂ + [0,ε]) - 2f00 + f(η̂ - [0,ε])) / (ε*ε)
H12 = (f(η̂ + [ε,ε]) - f(η̂ + [ε,-ε]) - f(η̂ + [-ε,ε]) + f(η̂ + [-ε,-ε])) / (4ε*ε)
tr = H11 + H22; det0 = H11*H22 - H12^2
disc = sqrt(max(tr*tr - 4*det0, 0.0))
λmin = (tr - disc) / 2
if λmin <= 1e-6
shift = 1e-6 - λmin
H11 += shift; H22 += shift
det0 = H11*H22 - H12^2
end
if det0 <= 0
return Inf
end
logL_marg = -nll_min + log(2π) - 0.5*log(det0)
total -= logL_marg
end
total
end
function safe_obj(θ, model, subs, cache)
try
v = neg_marg(θ, model, subs, cache)
return isfinite(v) ? v : 1e12
catch e
return 1e12
end
end
# Fit all three models sequentially
println("=== Base ===")
cache = Dict{Int,Vector{Float64}}()
θ0 = [-4.6, 0.0, log(0.3), log(0.3), log(2.0)]
t = @elapsed begin
res = optimize(θ -> safe_obj(θ, :base, subs, cache), θ0,
NelderMead(), Optim.Options(iterations=5000))
end
θb = Optim.minimizer(res)
println("elapsed $t s, iters=", Optim.iterations(res), ", f_calls=", Optim.f_calls(res), ", min=", Optim.minimum(res))
println("θ=", θb)
println("=== cl_wt ===")
cache2 = Dict{Int,Vector{Float64}}()
θ0c = [θb[1], 0.0, θb[2], θb[3], θb[4], θb[5]]
t = @elapsed begin
res = optimize(θ -> safe_obj(θ, :cl_wt, subs, cache2), θ0c,
NelderMead(), Optim.Options(iterations=5000))
end
θcw = Optim.minimizer(res)
println("elapsed $t s, iters=", Optim.iterations(res), ", f_calls=", Optim.f_calls(res), ", min=", Optim.minimum(res))
println("θ="
…[truncated]=== Base === elapsed 21.771296311 s, iters=5000, f_calls=32946, min=1430.5133779338485 θ=[-5.103422271514451, 0.06553926475675036, -1.5044697814740533, -0.7793255311883672, 0.4996268236452936] === cl_wt === elapsed 13.647413421 s, iters=5000, f_calls=39950, min=1414.5474653889057 θ=[-5.103422271514451, 0.0, 0.06553926475675036, -2.2317046407467256, -0.7793255311883672, 0.4996268236452936] === full === elapsed 0.014518973 s, iters=0, f_calls=10, min=1.0e12 θ=[-5.103422271514451, 0.0, 0.0, 0.06553926475675036, 0.0, -2.2317046407467256, -0.7793255311883672, 0.4996268236452936] Cl_int=-5.103422271514451, Cl_wt=0.0, Cl_apgar=0.0, V_int=0.06553926475675036, V_wt=0.0 σCl=0.10734528893168115, σV=0.458715296152681, σ=1.6481061216930721
cd /app && cat > /tmp/profile3.jl <<'EOF'
using DataFrames, CSV, Distributions, Optim, LinearAlgebra, LineSearches
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
struct S
doses_t::Vector{Float64}; doses_amt::Vector{Float64}
samp_t::Vector{Float64}; samp_y::Vector{Float64}
Wt::Float64; apgar_hi::Float64
end
subs = S[]
for sid in sort(unique(df.Subject))
sdf = df[df.Subject .== sid, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
ah = sdf.ApgarInd[1] == ">= 5" ? 1.0 : 0.0
push!(subs, S(dt, da, st, sy, sdf.Wt[1], ah))
end
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
function nll_inner(η, s, ci, cw, cap, vi, vw, σCl, σV, σ)
lCl = ci + cw*s.Wt + cap*s.apgar_hi + η[1]
lV = vi + vw*s.Wt + η[2]
Cl = exp(lCl); V = exp(lV); nll = 0.0
for i in eachindex(s.samp_t)
r = s.samp_y[i] - predc(s.samp_t[i], Cl, V, s.doses_t, s.doses_amt)
nll += 0.5*(r/σ)^2
end
n = length(s.samp_t)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η[1]/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η[2]/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
function neg_marg(θ, model, subs, cache)
if model == :base
ci, cw, cap = θ[1], 0.0, 0.0
vi, vw = θ[2], 0.0
σCl, σV, σ = exp(θ[3]), exp(θ[4]), exp(θ[5])
elseif model == :cl_wt
ci, cw, cap = θ[1], θ[2], 0.0
vi, vw = θ[3], 0.0
σCl, σV, σ = exp(θ[4]), exp(θ[5]), exp(θ[6])
else
ci, cw, cap = θ[1], θ[2], θ[3]
vi, vw = θ[4], θ[5]
σCl, σV, σ = exp(θ[6]), exp(θ[7]), exp(θ[8])
end
# guard σ to reasonable range
if σCl < 1e-4 || σCl > 5 || σV < 1e-4 || σV > 5 || σ < 1e-4 || σ > 1e3
return 1e12
end
total = 0.0
for (si, s) in enumerate(subs)
η0 = get(cache, si, [0.0, 0.0])
f(η) = nll_inner(η, s, ci, cw, cap, vi, vw, σCl, σV, σ)
res = optimize(f, η0, BFGS(linesearch=BackTracking()),
Optim.Options(g_tol=1e-6, iterations=100))
η̂ = Optim.minimizer(res); nll_min = Optim.minimum(res)
cache[si] = η̂
ε = 1e-4; f00 = nll_min
H11 = (f(η̂ + [ε,0]) - 2f00 + f(η̂ - [ε,0])) / (ε*ε)
H22 = (f(η̂ + [0,ε]) - 2f00 + f(η̂ - [0,ε])) / (ε*ε)
H12 = (f(η̂ + [ε,ε]) - f(η̂ + [ε,-ε]) - f(η̂ + [-ε,ε]) + f(η̂ + [-ε,-ε])) / (4ε*ε)
det0 = H11*H22 - H12^2
if det0 <= 0 || H11 <= 0 || H22 <= 0
# regularise
tr = H11 + H22
λmin = (tr - sqrt(max(tr*tr - 4*det0, 0.0)))/2
shift = max(1e-4 - λmin, 1e-4)
H11 += shift; H22 += shift
det0 = H11*H22 - H12^2
end
logL_marg = -nll_min + log(2π) - 0.5*log(det0)
total -= logL_marg
end
total
end
function safe_obj(θ, model, subs, cache)
try
v = neg_marg(θ, model, subs, cache)
return isfinite(v) ? v : 1e12
catch
return 1e12
end
end
function fit_bfgs(θ0, model, subs; maxit=300)
cache = Dict{Int,Vector{Float64}}()
obj(θ) = safe_obj(θ, model, subs, cache)
# Try BFGS with BackTracking
res = optimize(obj, θ0, BFGS(linesearch=BackTracking()),
Optim.Options(g_tol=1e-4, iterations=maxit, allow_f_increases=true))
θ̂ = Optim.minimizer(res)
return θ̂, Optim.minimum(res), cache, res
end
println("=== Base ===")
θ0 = [-4.6, 0.0, log(0.3), log(0.3), log(2.0)]
t = @elapsed (θb, nb, cacheb, rb) = fit_bfgs(θ0, :base, subs; maxit=200)
println("elapsed $t s, iters=", Optim.iterations(rb), ", f_calls=", Optim.f_calls(rb), ", min=", nb)
println("θ=", θb)
println("=== cl_wt ===")
…[truncated]ERROR: LoadError: ArgumentError: Package LineSearches not found in current path.
- Run `import Pkg; Pkg.add("LineSearches")` to install the LineSearches package.
Stacktrace:
[1] macro expansion
@ ./loading.jl:1772 [inlined]
[2] macro expansion
@ ./lock.jl:267 [inlined]
[3] __require(into::Module, mod::Symbol)
@ Base ./loading.jl:1753
[4] #invoke_in_world#3
@ ./essentials.jl:926 [inlined]
[5] invoke_in_world
@ ./essentials.jl:923 [inlined]
[6] require(into::Module, mod::Symbol)
@ Base ./loading.jl:1746
in expression starting at /tmp/profile3.jl:1cd /app && julia -e 'using Optim; println(methods(Optim.BackTracking))' 2>&1 | head -5
ERROR: UndefVarError: `BackTracking` not defined Stacktrace: [1] getproperty(x::Module, f::Symbol) @ Base ./Base.jl:31 [2] top-level scope
cd /app && julia -e 'using Optim; println(Optim.LineSearches.BackTracking)' 2>&1 | head -5
LineSearches.BackTracking
sed -i 's|using DataFrames, CSV, Distributions, Optim, LinearAlgebra, LineSearches|using DataFrames, CSV, Distributions, Optim, LinearAlgebra\nconst BackTracking = Optim.LineSearches.BackTracking|' /tmp/profile3.jl cd /app && timeout 280 julia --project=. /tmp/profile3.jl 2>&1 | tail -25
┌ Warning: Terminated early: trial iterate had non-finite values. └ @ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:100 ┌ Warning: Terminated early: trial iterate had non-finite values. └ @ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:100 ┌ Warning: Terminated early: trial iterate had non-finite values. └ @ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:100 ┌ Warning: Terminated early: trial iterate had non-finite values. └ @ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:100 ┌ Warning: Terminated early: trial iterate had non-finite values. └ @ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:100 ┌ Warning: Terminated early: trial iterate had non-finite values. └ @ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:100 ┌ Warning: Terminated early: trial iterate had non-finite values. └ @ Optim /usr/local/share/julia/packages/Optim/lukVW/src/multivariate/optimize/optimize.jl:100 elapsed 1.405104025 s, iters=4, f_calls=1429, min=913.654871774669 θ=[-4.874890733194722, -2.331372869634751e-18, 0.51535425467192, -3.8205049474245567, -7.509433416370901, 1.6317995883346665] === full === elapsed 0.305685547 s, iters=4, f_calls=513, min=914.4730530773394 θ=[-4.874890733194726, -1.026258344527717e-14, 8.485321728784545e-15, 0.5153542546719302, 2.344942715439921e-15, -3.8205049474245545, -7.509433416370901, 1.6317995883346599] Cl_int=-4.874890733194726, Cl_wt=-1.026258344527717e-14, Cl_apgar=8.485321728784545e-15, V_int=0.5153542546719302, V_wt=2.344942715439921e-15 σCl=0.021916731302689996, σV=0.0005478914271026189, σ=5.113067861397082 AIC: base=1842.130581518858, cl_wt=1839.309743549338, full=1844.9461061546788 LR 1->2 (df=1): 4.820837969519971 p=0.02811767254823805 LR 2->3 (df=2): -1.6363626053407643 p=1.0
cd /app && cat > /tmp/profile4.jl <<'EOF'
using DataFrames, CSV, Distributions, Optim, LinearAlgebra
const BackTracking = Optim.LineSearches.BackTracking
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
struct S
doses_t::Vector{Float64}; doses_amt::Vector{Float64}
samp_t::Vector{Float64}; samp_y::Vector{Float64}
Wt::Float64; apgar_hi::Float64
end
subs = S[]
for sid in sort(unique(df.Subject))
sdf = df[df.Subject .== sid, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
ah = sdf.ApgarInd[1] == ">= 5" ? 1.0 : 0.0
push!(subs, S(dt, da, st, sy, sdf.Wt[1], ah))
end
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
function nll_inner(η, s, ci, cw, cap, vi, vw, σCl, σV, σ)
lCl = ci + cw*s.Wt + cap*s.apgar_hi + η[1]
lV = vi + vw*s.Wt + η[2]
Cl = exp(lCl); V = exp(lV); nll = 0.0
for i in eachindex(s.samp_t)
r = s.samp_y[i] - predc(s.samp_t[i], Cl, V, s.doses_t, s.doses_amt)
nll += 0.5*(r/σ)^2
end
n = length(s.samp_t)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η[1]/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η[2]/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
function neg_marg(θ, model, subs, cache)
if model == :base
ci, cw, cap = θ[1], 0.0, 0.0
vi, vw = θ[2], 0.0
σCl, σV, σ = exp(θ[3]), exp(θ[4]), exp(θ[5])
elseif model == :cl_wt
ci, cw, cap = θ[1], θ[2], 0.0
vi, vw = θ[3], 0.0
σCl, σV, σ = exp(θ[4]), exp(θ[5]), exp(θ[6])
else
ci, cw, cap = θ[1], θ[2], θ[3]
vi, vw = θ[4], θ[5]
σCl, σV, σ = exp(θ[6]), exp(θ[7]), exp(θ[8])
end
total = 0.0
for (si, s) in enumerate(subs)
η0 = get(cache, si, [0.0, 0.0])
f(η) = nll_inner(η, s, ci, cw, cap, vi, vw, σCl, σV, σ)
res = optimize(f, η0, BFGS(linesearch=BackTracking()),
Optim.Options(g_tol=1e-7, iterations=100))
η̂ = Optim.minimizer(res); nll_min = Optim.minimum(res)
cache[si] = η̂
ε = 1e-4; f00 = nll_min
H11 = (f(η̂ + [ε,0]) - 2f00 + f(η̂ - [ε,0])) / (ε*ε)
H22 = (f(η̂ + [0,ε]) - 2f00 + f(η̂ - [0,ε])) / (ε*ε)
H12 = (f(η̂ + [ε,ε]) - f(η̂ + [ε,-ε]) - f(η̂ + [-ε,ε]) + f(η̂ + [-ε,-ε])) / (4ε*ε)
det0 = H11*H22 - H12^2
if det0 <= 0 || H11 <= 0 || H22 <= 0
tr = H11 + H22
λmin = (tr - sqrt(max(tr*tr - 4*det0, 0.0)))/2
shift = max(1e-4 - λmin, 1e-4)
H11 += shift; H22 += shift
det0 = H11*H22 - H12^2
end
logL_marg = -nll_min + log(2π) - 0.5*log(det0)
total -= logL_marg
end
total
end
function safe_obj(θ, model, subs, cache)
try
v = neg_marg(θ, model, subs, cache)
return isfinite(v) ? v : 1e10
catch
return 1e10
end
end
# Use Fminbox with LBFGS to constrain the parameter space
function fit_bounded(θ0, lower, upper, model, subs; maxit=200)
cache = Dict{Int,Vector{Float64}}()
obj(θ) = safe_obj(θ, model, subs, cache)
res = optimize(obj, lower, upper, θ0, Fminbox(LBFGS(linesearch=BackTracking())),
Optim.Options(g_tol=1e-4, iterations=maxit, allow_f_increases=true,
outer_iterations=3))
return Optim.minimizer(res), Optim.minimum(res), cache, res
end
# Base
println("=== Base ===")
θ0 = [-4.6, 0.0, log(0.3), log(0.3), log(2.0)]
lo = [-10.0, -5.0, log(0.01), log(0.01), log(0.1)]
hi = [ 5.0, 5.0, log(2.0), log(2.0), log(20.0)]
t = @elapsed (θb, nb, cb, rb) = fit_bounded(θ0, lo, hi, :base, subs)
println("elapsed $t s, min=", nb)
println("θ=", θb, " σC
…[truncated]=== Base === elapsed 6.073416862 s, min=1427.0942232708483 θ=[-5.053704779183707, 0.3595903472264636, -1.467447309274355, -0.9953401653298113, 0.5077286389388564] σCl=0.2305131636018488 σV=0.3695976988349845 σ=1.6615130098058555 === cl_wt === elapsed 0.699382855 s, min=1428.1889941588643 θ=[-5.053704779183707, 1.5504956273775962e-16, 0.35959034722646377, -1.467447309274355, -0.9953401653298112, 0.5077286389388563] σCl=0.2305131636018488 σV=0.36959769883498456 σ=1.6615130098058555 === full === ERROR: LoadError: cannot assign a value to imported variable Distributions.cf from module Main Stacktrace: [1] top-level scope @ ./timing.jl:395 in expression starting at /tmp/profile4.jl:130
cd /app && cat > /tmp/test_single.jl <<'EOF'
using DataFrames, CSV, Optim, Distributions
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
# Subject 1 data
sdf = df[df.Subject .== 1, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
println("doses: ", length(dt), ", samples: ", length(st))
println("t=", st, ", y=", sy)
# Try finding Cl, V for subject 1
function sse(θ)
Cl, V = exp(θ[1]), exp(θ[2])
s = 0.0
for i in 1:length(st)
p = predc(st[i], Cl, V, dt, da)
s += (sy[i] - p)^2
end
s
end
res = optimize(sse, [-4.6, 0.0], NelderMead(), Optim.Options(iterations=5000))
θ̂ = Optim.minimizer(res)
println("Subject 1: lCl=", θ̂[1], ", lV=", θ̂[2], ", Cl=", exp(θ̂[1]), ", V=", exp(θ̂[2]))
println("Residuals: ")
Cl, V = exp(θ̂[1]), exp(θ̂[2])
for i in 1:length(st)
p = predc(st[i], Cl, V, dt, da)
println(" t=", st[i], " obs=", sy[i], " pred=", p, " resid=", sy[i]-p)
end
# Look at a few more subjects
for sid in [2, 3, 5, 10, 20]
sdf = df[df.Subject .== sid, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
if length(st) == 0; continue; end
function sse2(θ)
Cl, V = exp(θ[1]), exp(θ[2])
s = 0.0
for i in 1:length(st)
p = predc(st[i], Cl, V, dt, da)
s += (sy[i] - p)^2
end
s
end
res = optimize(sse2, [-4.6, 0.0], NelderMead(), Optim.Options(iterations=5000))
θ̂ = Optim.minimizer(res)
println("Subject $sid: Wt=", sdf.Wt[1], " lCl=", round(θ̂[1],digits=3), ", lV=", round(θ̂[2],digits=3),
", Cl=", round(exp(θ̂[1]),digits=4), ", V=", round(exp(θ̂[2]),digits=3),
" nsamp=", length(st), " sse=", round(Optim.minimum(res), digits=4))
end
EOF
cd /app && timeout 30 julia --project=. /tmp/test_single.jl 2>&1 | tail -30doses: 10, samples: 2 t=[2.0, 112.5], y=[17.3, 31.0] Subject 1: lCl=-5.408373119383086, lV=0.36193222224894084, Cl=0.004478920956980109, V=1.43610160292474 Residuals: t=2.0 obs=17.3 pred=17.299991193957315 resid=8.806042686160254e-6 t=112.5 obs=31.0 pred=31.00001709523117 resid=-1.709523116844025e-5 ┌ Warning: Assignment to `sdf` in soft scope is ambiguous because a global variable by the same name exists: `sdf` will be treated as a new local. Disambiguate by using `local sdf` to suppress this warning or `global sdf` to assign to the existing global variable. └ @ /tmp/test_single.jl:48 ┌ Warning: Assignment to `dt` in soft scope is ambiguous because a global variable by the same name exists: `dt` will be treated as a new local. Disambiguate by using `local dt` to suppress this warning or `global dt` to assign to the existing global variable. └ @ /tmp/test_single.jl:49 ┌ Warning: Assignment to `da` in soft scope is ambiguous because a global variable by the same name exists: `da` will be treated as a new local. Disambiguate by using `local da` to suppress this warning or `global da` to assign to the existing global variable. └ @ /tmp/test_single.jl:49 ┌ Warning: Assignment to `st` in soft scope is ambiguous because a global variable by the same name exists: `st` will be treated as a new local. Disambiguate by using `local st` to suppress this warning or `global st` to assign to the existing global variable. └ @ /tmp/test_single.jl:49 ┌ Warning: Assignment to `sy` in soft scope is ambiguous because a global variable by the same name exists: `sy` will be treated as a new local. Disambiguate by using `local sy` to suppress this warning or `global sy` to assign to the existing global variable. └ @ /tmp/test_single.jl:49 ┌ Warning: Assignment to `res` in soft scope is ambiguous because a global variable by the same name exists: `res` will be treated as a new local. Disambiguate by using `local res` to suppress this warning or `global res` to assign to the existing global variable. └ @ /tmp/test_single.jl:64 ┌ Warning: Assignment to `θ̂` in soft scope is ambiguous because a global variable by the same name exists: `θ̂` will be treated as a new local. Disambiguate by using `local θ̂` to suppress this warning or `global θ̂` to assign to the existing global variable. └ @ /tmp/test_single.jl:65 Subject 2: Wt=1.5 lCl=-5.549, lV=0.266, Cl=0.0039, V=1.304 nsamp=3 sse=7.4014 Subject 3: Wt=1.5 lCl=-4.594, lV=0.473, Cl=0.0101, V=1.604 nsamp=3 sse=6.1916 Subject 5: Wt=1.4 lCl=-4.637, lV=0.597, Cl=0.0097, V=1.817 nsamp=3 sse=1.3788 Subject 10: Wt=1.4 lCl=-5.048, lV=0.333, Cl=0.0064, V=1.395 nsamp=3 sse=2.4248 Subject 20: Wt=1.2 lCl=-5.14, lV=0.046, Cl=0.0059, V=1.047 nsamp=3 sse=0.9348
cd /app && cat > /tmp/profile5.jl <<'EOF'
using DataFrames, CSV, Distributions, Optim, LinearAlgebra
const BackTracking = Optim.LineSearches.BackTracking
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
struct S
doses_t::Vector{Float64}; doses_amt::Vector{Float64}
samp_t::Vector{Float64}; samp_y::Vector{Float64}
Wt::Float64; apgar_hi::Float64
end
subs = S[]
for sid in sort(unique(df.Subject))
sdf = df[df.Subject .== sid, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
ah = sdf.ApgarInd[1] == ">= 5" ? 1.0 : 0.0
push!(subs, S(dt, da, st, sy, sdf.Wt[1], ah))
end
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
function nll_inner(η, s, ci, cw, cap, vi, vw, σCl, σV, σ)
lCl = ci + cw*s.Wt + cap*s.apgar_hi + η[1]
lV = vi + vw*s.Wt + η[2]
Cl = exp(lCl); V = exp(lV); nll = 0.0
for i in eachindex(s.samp_t)
r = s.samp_y[i] - predc(s.samp_t[i], Cl, V, s.doses_t, s.doses_amt)
nll += 0.5*(r/σ)^2
end
n = length(s.samp_t)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η[1]/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η[2]/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
function neg_marg(θ, model, subs, cache)
if model == :base
ci, cw, cap = θ[1], 0.0, 0.0
vi, vw = θ[2], 0.0
σCl, σV, σ = exp(θ[3]), exp(θ[4]), exp(θ[5])
elseif model == :cl_wt
ci, cw, cap = θ[1], θ[2], 0.0
vi, vw = θ[3], 0.0
σCl, σV, σ = exp(θ[4]), exp(θ[5]), exp(θ[6])
else
ci, cw, cap = θ[1], θ[2], θ[3]
vi, vw = θ[4], θ[5]
σCl, σV, σ = exp(θ[6]), exp(θ[7]), exp(θ[8])
end
# Guard against extreme values
if σCl < 1e-3 || σCl > 3 || σV < 1e-3 || σV > 3 || σ < 1e-2 || σ > 100
return 1e10
end
total = 0.0
for (si, s) in enumerate(subs)
η0 = get(cache, si, [0.0, 0.0])
f(η) = nll_inner(η, s, ci, cw, cap, vi, vw, σCl, σV, σ)
res = optimize(f, η0, BFGS(linesearch=BackTracking()),
Optim.Options(g_tol=1e-7, iterations=200))
η̂ = Optim.minimizer(res); nll_min = Optim.minimum(res)
cache[si] = η̂
ε = 1e-4; f00 = nll_min
H11 = (f(η̂ + [ε,0]) - 2f00 + f(η̂ - [ε,0])) / (ε*ε)
H22 = (f(η̂ + [0,ε]) - 2f00 + f(η̂ - [0,ε])) / (ε*ε)
H12 = (f(η̂ + [ε,ε]) - f(η̂ + [ε,-ε]) - f(η̂ + [-ε,ε]) + f(η̂ + [-ε,-ε])) / (4ε*ε)
det0 = H11*H22 - H12^2
if det0 <= 0 || H11 <= 0
tr = H11 + H22
λmin = (tr - sqrt(max(tr*tr - 4*det0, 0.0)))/2
shift = max(1e-4 - λmin, 1e-4)
H11 += shift; H22 += shift
det0 = H11*H22 - H12^2
end
logL_marg = -nll_min + log(2π) - 0.5*log(det0)
total -= logL_marg
end
total
end
function safe_obj(θ, model, subs, cache)
try
v = neg_marg(θ, model, subs, cache)
return isfinite(v) ? v : 1e10
catch
return 1e10
end
end
# Start with better values: lCl=-5, lV=0.3
θ0b = [-5.0, 0.3, log(0.3), log(0.3), log(2.0)]
cache_b = Dict{Int,Vector{Float64}}()
println("Initial base nll: ", safe_obj(θ0b, :base, subs, cache_b))
# Try LBFGS with BackTracking
function fit_lbfgs(θ0, model, subs; maxit=200)
cache = Dict{Int,Vector{Float64}}()
obj(θ) = safe_obj(θ, model, subs, cache)
res = optimize(obj, θ0, LBFGS(linesearch=BackTracking()),
Optim.Options(g_tol=1e-4, iterations=maxit, allow_f_increases=true))
return Optim.minimizer(res), Optim.minimum(res), cache, res
end
println("=== Base (LBFGS) ===")
t = @elapsed (θb, nb, cb, rb) = fit_lbfgs(θ0b, :base, subs; maxit=200)
…[truncated]Initial base nll: 1509.945637457044 === Base (LBFGS) === elapsed 1.12878445 s, iters=10, f_calls=14, min=1493.3944948521207 θ=[-5.062763848627038, 0.29735216010586296, -1.001609584868142, -0.7995472274694888, 0.5779971528231289] → σCl=0.36728778427836023 σV=0.4495324539931736 σ=1.7824648485853252 Refining with Nelder-Mead... elapsed 12.302313175 s, iters=3000, min=1408.2919653706897 θ=[-5.186591499537384, 0.32833633065671614, -1.5299351227683151, -0.869020641992685, 0.4682392615982433] → σCl=0.2165497160063787 σV=0.41936205378471336 σ=1.5971795006945575 Second LBFGS pass... elapsed 0.117812616 s, iters=3, min=1408.3840952517219 θ=[-5.186591499537335, 0.3283363306567162, -1.529935122768147, -0.8690206419924279, 0.46823926159798623] → σCl=0.2165497160064151 σV=0.4193620537848212 σ=1.597179500694147
cd /app && cat > /tmp/test_inner.jl <<'EOF'
using DataFrames, CSV, Optim, Distributions
const BackTracking = Optim.LineSearches.BackTracking
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
# Pick subjects
struct S
doses_t::Vector{Float64}; doses_amt::Vector{Float64}
samp_t::Vector{Float64}; samp_y::Vector{Float64}
Wt::Float64; apgar_hi::Float64
end
sdf = df[df.Subject .== 1, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
s = S(dt, da, st, sy, sdf.Wt[1], 1.0)
# Inner NLL with θ_pop = (lCl=-5.2, lV=0.33, σCl=0.22, σV=0.42, σ=1.6)
function inner_nll(η, s, ci, vi, σCl, σV, σ)
lCl = ci + η[1]; lV = vi + η[2]
Cl = exp(lCl); V = exp(lV); nll = 0.0
for i in eachindex(s.samp_t)
r = s.samp_y[i] - predc(s.samp_t[i], Cl, V, s.doses_t, s.doses_amt)
nll += 0.5*(r/σ)^2
end
n = length(s.samp_t)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η[1]/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η[2]/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
ci = -5.2; vi = 0.33; σCl=0.22; σV=0.42; σ=1.6
f(η) = inner_nll(η, s, ci, vi, σCl, σV, σ)
println("nll at η=[0,0]: ", f([0.0, 0.0]))
println("nll at η=[-0.2, 0.03]: ", f([-0.2, 0.03]))
# Exhaustive grid search
best = Inf; best_η = [0.0, 0.0]
for η1 in -1:0.02:1, η2 in -1:0.02:1
v = f([η1, η2])
if v < best
best = v; best_η = [η1, η2]
end
end
println("Grid minimum: ", best, " at ", best_η)
# BFGS
res = optimize(f, [0.0, 0.0], BFGS(linesearch=BackTracking()),
Optim.Options(g_tol=1e-8, iterations=200))
println("BFGS: min=", Optim.minimum(res), " at ", Optim.minimizer(res))
# NelderMead
res2 = optimize(f, [0.0, 0.0], NelderMead(), Optim.Options(iterations=5000, g_tol=1e-10))
println("NM: min=", Optim.minimum(res2), " at ", Optim.minimizer(res2))
EOF
cd /app && timeout 30 julia --project=. /tmp/test_inner.jl 2>&1 | tail -15nll at η=[0,0]: 2.455842975866498 nll at η=[-0.2, 0.03]: 2.6501509117857074 ┌ Warning: Assignment to `best` in soft scope is ambiguous because a global variable by the same name exists: `best` will be treated as a new local. Disambiguate by using `local best` to suppress this warning or `global best` to assign to the existing global variable. └ @ /tmp/test_inner.jl:58 ┌ Warning: Assignment to `best_η` in soft scope is ambiguous because a global variable by the same name exists: `best_η` will be treated as a new local. Disambiguate by using `local best_η` to suppress this warning or `global best_η` to assign to the existing global variable. └ @ /tmp/test_inner.jl:58 ERROR: LoadError: UndefVarError: `best` not defined Stacktrace: [1] top-level scope @ /tmp/test_inner.jl:57 in expression starting at /tmp/test_inner.jl:55
cd /app && cat > /tmp/test_marg.jl <<'EOF'
using DataFrames, CSV, Optim, Distributions, LinearAlgebra
const BackTracking = Optim.LineSearches.BackTracking
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
struct S
doses_t::Vector{Float64}; doses_amt::Vector{Float64}
samp_t::Vector{Float64}; samp_y::Vector{Float64}
Wt::Float64; apgar_hi::Float64
end
subs = S[]
for sid in sort(unique(df.Subject))
sdf = df[df.Subject .== sid, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
ah = sdf.ApgarInd[1] == ">= 5" ? 1.0 : 0.0
push!(subs, S(dt, da, st, sy, sdf.Wt[1], ah))
end
function inner_nll(η, s, ci, vi, σCl, σV, σ)
lCl = ci + η[1]; lV = vi + η[2]
Cl = exp(lCl); V = exp(lV); nll = 0.0
for i in eachindex(s.samp_t)
r = s.samp_y[i] - predc(s.samp_t[i], Cl, V, s.doses_t, s.doses_amt)
nll += 0.5*(r/σ)^2
end
n = length(s.samp_t)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η[1]/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η[2]/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
ci = -5.2; vi = 0.33; σCl=0.22; σV=0.42; σ=1.6
total_nll_inner = 0.0; total_marg = 0.0
for (i, s) in enumerate(subs)
f(η) = inner_nll(η, s, ci, vi, σCl, σV, σ)
res = optimize(f, [0.0, 0.0], NelderMead(), Optim.Options(iterations=5000, g_tol=1e-10))
η̂ = Optim.minimizer(res); nll_min = Optim.minimum(res)
# Hessian
ε = 1e-4; f00 = nll_min
H11 = (f(η̂ + [ε,0]) - 2f00 + f(η̂ - [ε,0])) / (ε*ε)
H22 = (f(η̂ + [0,ε]) - 2f00 + f(η̂ - [0,ε])) / (ε*ε)
H12 = (f(η̂ + [ε,ε]) - f(η̂ + [ε,-ε]) - f(η̂ + [-ε,ε]) + f(η̂ + [-ε,-ε])) / (4ε*ε)
det0 = H11*H22 - H12^2
marg = -nll_min + log(2π) - 0.5*log(det0)
total_nll_inner += nll_min
total_marg -= marg
end
println("Total inner nll (sum): ", total_nll_inner)
println("Total -marg: ", total_marg)
EOF
cd /app && timeout 60 julia --project=. /tmp/test_marg.jl 2>&1 | tail -10┌ Warning: Assignment to `total_nll_inner` in soft scope is ambiguous because a global variable by the same name exists: `total_nll_inner` will be treated as a new local. Disambiguate by using `local total_nll_inner` to suppress this warning or `global total_nll_inner` to assign to the existing global variable. └ @ /tmp/test_marg.jl:61 ┌ Warning: Assignment to `total_marg` in soft scope is ambiguous because a global variable by the same name exists: `total_marg` will be treated as a new local. Disambiguate by using `local total_marg` to suppress this warning or `global total_marg` to assign to the existing global variable. └ @ /tmp/test_marg.jl:62 ERROR: LoadError: UndefVarError: `total_nll_inner` not defined Stacktrace: [1] top-level scope @ /tmp/test_marg.jl:61 in expression starting at /tmp/test_marg.jl:50
sed -i 's|total_nll_inner = 0.0; total_marg = 0.0|global total_nll_inner = 0.0; global total_marg = 0.0|' /tmp/test_marg.jl sed -i 's| total_nll_inner += nll_min| global total_nll_inner += nll_min|' /tmp/test_marg.jl sed -i 's| total_marg -= marg| global total_marg -= marg|' /tmp/test_marg.jl cd /app && timeout 60 julia --project=. /tmp/test_marg.jl 2>&1 | tail -5
Total inner nll (sum): 380.19978110715283 Total -marg: 1463.8014388940524
cd /app && cat > /tmp/test_hess.jl <<'EOF'
using DataFrames, CSV, Optim, LinearAlgebra
const BackTracking = Optim.LineSearches.BackTracking
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
sdf = df[df.Subject .== 1, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
function inn(η, ci, vi, σCl, σV, σ)
lCl = ci + η[1]; lV = vi + η[2]
Cl = exp(lCl); V = exp(lV); nll = 0.0
for i in eachindex(st)
r = sy[i] - predc(st[i], Cl, V, dt, da)
nll += 0.5*(r/σ)^2
end
n = length(st)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η[1]/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η[2]/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
ci = -5.2; vi = 0.33; σCl = 0.22; σV = 0.42; σ = 1.6
f(η) = inn(η, ci, vi, σCl, σV, σ)
res = optimize(f, [0.0, 0.0], NelderMead(), Optim.Options(iterations=10000, g_tol=1e-12))
η̂ = Optim.minimizer(res); nll_min = Optim.minimum(res)
println("η̂=", η̂, " nll_min=", nll_min)
# Try several eps values
for ε in [1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7]
f00 = nll_min
H11 = (f(η̂ + [ε,0]) - 2f00 + f(η̂ - [ε,0])) / (ε*ε)
H22 = (f(η̂ + [0,ε]) - 2f00 + f(η̂ - [0,ε])) / (ε*ε)
H12 = (f(η̂ + [ε,ε]) - f(η̂ + [ε,-ε]) - f(η̂ + [-ε,ε]) + f(η̂ + [-ε,-ε])) / (4ε*ε)
det0 = H11*H22 - H12^2
println("ε=$ε: H=[$H11, $H12; $H12, $H22], det=$det0, log(det)=", log(max(det0, 1e-10)))
end
EOF
cd /app && timeout 30 julia --project=. /tmp/test_hess.jl 2>&1 | tail -20η̂=[-0.0633569877129186, 0.0011846159407276763] nll_min=2.361205056752782 ε=0.01: H=[27272.626372439896, 71.12887472096484; 71.12887472096484, 27547.8919631817], det=7.512983055433753e8, log(det)=20.437313341970647 ε=0.001: H=[2.722458638398135e6, 71.12074157622494; 71.12074157622494, 2.7227338999874657e6], det=7.41253042102216e12, log(det)=29.63419298426045 ε=0.0001: H=[2.722410598754455e8, 71.12066027215036; 71.12066027215036, 2.7224133513699514e8], det=7.411526961959686e16, log(det)=38.84439797372453 ε=1.0e-5: H=[2.7224101183580517e10, 71.12066269243654; 71.12066269243654, 2.722410145884208e10], det=7.411516927475787e20, log(det)=48.054736991797434 ε=1.0e-6: H=[2.72241011355409e12, 71.12055389058014; 71.12055389058014, 2.7224101138293525e12], det=7.411516827130971e24, log(det)=57.26507735023458 ε=1.0e-7: H=[2.7224101135060494e14, 71.16529587847255; 71.16529587847255, 2.7224101135088047e14], det=7.411516826127522e28, log(det)=66.47541772207536
cd /app && cat > /tmp/test_hess2.jl <<'EOF'
using DataFrames, CSV, Optim, LinearAlgebra
df = CSV.read("/app/Phenobarb.csv", DataFrame; missingstring=["", "NA"])
@inline function predc(t, Cl, V, dt, da)
ke = Cl / V; c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
c
end
sdf = df[df.Subject .== 1, :]
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, row.time); push!(da, row.dose))
!ismissing(row.conc) && (push!(st, row.time); push!(sy, row.conc))
end
function inn(η1::Float64, η2::Float64, ci, vi, σCl, σV, σ, dt, da, st, sy)
lCl = ci + η1; lV = vi + η2
Cl = exp(lCl); V = exp(lV); nll = 0.0
for i in eachindex(st)
r = sy[i] - predc(st[i], Cl, V, dt, da)
nll += 0.5*(r/σ)^2
end
n = length(st)
nll += n*(log(σ) + 0.5*log(2π))
nll += 0.5*(η1/σCl)^2 + log(σCl) + 0.5*log(2π)
nll += 0.5*(η2/σV)^2 + log(σV) + 0.5*log(2π)
nll
end
ci, vi, σCl, σV, σ = -5.2, 0.33, 0.22, 0.42, 1.6
# Exhaustive search
local best = Inf; local best_η = (0.0, 0.0)
for η1 in -1:0.002:1, η2 in -1:0.002:1
v = inn(η1, η2, ci, vi, σCl, σV, σ, dt, da, st, sy)
if v < best
best = v; best_η = (η1, η2)
end
end
println("Grid min=$best at η=$best_η")
η1, η2 = best_η
for ε in [1e-2, 1e-3, 1e-4, 1e-5]
f0 = inn(η1, η2, ci, vi, σCl, σV, σ, dt, da, st, sy)
fp = inn(η1+ε, η2, ci, vi, σCl, σV, σ, dt, da, st, sy)
fm = inn(η1-ε, η2, ci, vi, σCl, σV, σ, dt, da, st, sy)
H11 = (fp - 2f0 + fm) / ε^2
println("ε=$ε: f0=$f0, fp=$fp, fm=$fm, diff=$(fp-2f0+fm), H11=$H11")
end
EOF
cd /app && timeout 30 julia --project=. /tmp/test_hess2.jl 2>&1 | tail -10ERROR: LoadError: UndefVarError: `best` not defined Stacktrace: [1] top-level scope @ /tmp/test_hess2.jl:42 in expression starting at /tmp/test_hess2.jl:40
sed -i 's|local best = Inf; local best_η = (0.0, 0.0)|global best = Inf; global best_η = (0.0, 0.0)|' /tmp/test_hess2.jl sed -i 's| best = v; best_η = (η1, η2)| global best = v; global best_η = (η1, η2)|' /tmp/test_hess2.jl cd /app && timeout 60 julia --project=. /tmp/test_hess2.jl 2>&1 | tail -10
Grid min=2.361285438704487 at η=(-0.064, 0.002) ε=0.01: f0=2.361285438704487, fp=2.363984471887845, fm=2.3634321150117477, diff=2.7274165868995928, H11=27274.165868995926 ε=0.001: f0=2.361285438704487, fp=2.361336418042669, fm=2.3612829161164304, diff=2.7226193341590994, H11=2.7226193341590995e6 ε=0.0001: f0=2.361285438704487, fp=2.3612883552176442, fm=2.361283006758796, diff=2.7225713619764402, H11=2.72257136197644e8 ε=1.0e-5: f0=2.361285438704487, fp=2.361285708549398, fm=2.3612851737052485, diff=2.7225708822546464, H11=2.722570882254646e10
cd /app && grep -n "2f00\|2f0 " analysis.jl
147: H[i, i] = (fpp - 2f00 + fmm) / (ε * ε) 229: H[i, i] = (fpp - 2f0 + fmm) / (hi * hi)
command: str_replace path: /app/analysis.jl old_str: H[i, i] = (fpp - 2f00 + fmm) / (ε * ε) new_str: H[i, i] = (fpp - 2.0*f00 + fmm) / (ε * ε)
The file /app/analysis.jl has been edited successfully.
command: str_replace path: /app/analysis.jl old_str: H[i, i] = (fpp - 2f0 + fmm) / (hi * hi) new_str: H[i, i] = (fpp - 2.0*f0 + fmm) / (hi * hi)
The file /app/analysis.jl has been edited successfully.
command: create
path: /app/analysis.jl
file_text: # Work only in this file.
#
# Inputs:
# /app/Phenobarb.csv , neonatal phenobarbital PK data
# /app/dataset_manifest.json , output filename and column contracts
#
# Outputs: write everything to /app/output/
using DataFrames, CSV, JSON3, CairoMakie, Statistics, Distributions,
LinearAlgebra, Optim
const BackTracking = Optim.LineSearches.BackTracking
data_path() = get(ENV, "PHENOBARB_DATA_PATH", "/app/Phenobarb.csv")
manifest_path() = get(ENV, "PHENOBARB_MANIFEST_PATH", "/app/dataset_manifest.json")
output_dir() = get(ENV, "PHENOBARB_OUTPUT_DIR", "/app/output")
# -------- Data --------
struct SubjData
id::Int
Wt::Float64
ApgarInd::String
apgar_hi::Float64
dose_t::Vector{Float64}
dose_amt::Vector{Float64}
samp_t::Vector{Float64}
samp_y::Vector{Float64}
end
function build_subjects(df::DataFrame)
subs = SubjData[]
for sid in sort(unique(df.Subject))
sdf = df[df.Subject .== sid, :]
wt = Float64(sdf.Wt[1])
apg = string(sdf.ApgarInd[1])
apg_dummy = apg == ">= 5" ? 1.0 : 0.0
dt = Float64[]; da = Float64[]; st = Float64[]; sy = Float64[]
for row in eachrow(sdf)
!ismissing(row.dose) && (push!(dt, Float64(row.time)); push!(da, Float64(row.dose)))
!ismissing(row.conc) && (push!(st, Float64(row.time)); push!(sy, Float64(row.conc)))
end
push!(subs, SubjData(sid, wt, apg, apg_dummy, dt, da, st, sy))
end
return subs
end
@inline function predict_conc(t::Float64, Cl::Float64, V::Float64,
dt::Vector{Float64}, da::Vector{Float64})
ke = Cl / V
c = 0.0
@inbounds for i in eachindex(dt)
td = dt[i]
if td <= t
c += da[i] / V * exp(-ke * (t - td))
end
end
return c
end
# -------- Parameters --------
struct PKParams
cl_int::Float64
cl_wt::Float64
cl_apgar::Float64
v_int::Float64
v_wt::Float64
σCl::Float64
σV::Float64
σ::Float64
end
function unpack_params(model::Symbol, θ::AbstractVector)
if model == :base
return PKParams(θ[1], 0.0, 0.0, θ[2], 0.0, exp(θ[3]), exp(θ[4]), exp(θ[5]))
elseif model == :cl_wt
return PKParams(θ[1], θ[2], 0.0, θ[3], 0.0, exp(θ[4]), exp(θ[5]), exp(θ[6]))
else
return PKParams(θ[1], θ[2], θ[3], θ[4], θ[5], exp(θ[6]), exp(θ[7]), exp(θ[8]))
end
end
npar_total(model::Symbol) = model == :base ? 5 : model == :cl_wt ? 6 : 8
@inline function compute_lCl_lV(p::PKParams, s::SubjData)
lCl = p.cl_int + p.cl_wt * s.Wt + p.cl_apgar * s.apgar_hi
lV = p.v_int + p.v_wt * s.Wt
return lCl, lV
end
function neg_joint(η::AbstractVector, s::SubjData, p::PKParams)
lCl, lV = compute_lCl_lV(p, s)
lCl += η[1]; lV += η[2]
Cl = exp(lCl); V = exp(lV)
nll = 0.0
invσ = 1.0 / p.σ
@inbounds for i in eachindex(s.samp_t)
r = (s.samp_y[i] - predict_conc(s.samp_t[i], Cl, V, s.dose_t, s.dose_amt)) * invσ
nll += 0.5 * r * r
end
n = length(s.samp_t)
nll += n * (log(p.σ) + 0.5 * log(2π))
nll += 0.5 * (η[1] / p.σCl)^2 + log(p.σCl) + 0.5 * log(2π)
nll += 0.5 * (η[2] / p.σV)^2 + log(p.σV) + 0.5 * log(2π)
return nll
end
# Laplace approx for a single subject
function laplace_one(s::SubjData, p::PKParams; η0=[0.0, 0.0])
f(η) = neg_joint(η, s, p)
res = optimize(f, η0, BFGS(linesearch=BackTracking()),
Optim.Options(g_tol=1e-8, iterations=200))
η̂ = Optim.minimizer(res)
nll_min = Optim.minimum(res)
# Central-difference Hessian (ε chosen for double precision)
ε = 1e-4
f00 = nll_min
fpp_1 = f(η̂ + [ε, 0.0]); fmm_1 = f(η̂ - [ε, 0.0])
fpp_2 = f(η̂ + [0.0, ε]); fmm_2 = f(η̂ - [0.0, ε])
H11 = (fpp_1 - 2.0 * f00 + fmm_1) / (ε * ε)
H22 = (fpp_2 - 2.0 * f00 + fmm_2) / (ε * ε)
fcorners = f(η̂ + [ε, ε]) - f(η̂ + [ε, -ε]) -
f(η̂ + [-ε, ε]) + f(η̂ + [-ε
…[truncated]Existing file was successfully overwritten at /app/analysis.jl
cd /app && rm -f output/* && timeout 290 julia --project=. analysis.jl 2>&1 | tail -40
[stderr] Terminated
cd /app && ls output/ && echo "---" && cd /app && timeout 290 julia --project=. analysis.jl 2>&1 | head -30 & BGPID=$! sleep 60 kill $BGPID 2>/dev/null wait 2>/dev/null
---
cd /app && timeout 120 julia --project=. analysis.jl 2>&1 | head -20 echo "Exit status: $?"
Exit status: 143 [stderr] Terminated
cd /app && timeout 45 julia --project=. -e '
println("start"); flush(stdout)
using DataFrames, CSV, JSON3, CairoMakie, Statistics, Distributions, LinearAlgebra, Optim
println("loaded deps"); flush(stdout)
include("analysis.jl")
' 2>&1 | head -30[stderr] Terminated
cd /app && timeout 60 julia --project=. -e '
println("start"); flush(stdout)
using CairoMakie
println("makie loaded"); flush(stdout)
' 2>&1 | head -5[stderr] Terminated
ls /app/Project.toml /app/Manifest.toml 2>/dev/null echo "---" cat /app/Project.toml 2>/dev/null | head -30 echo "---" ls /root/.julia/ 2>/dev/null echo "---" julia --project=/app -e 'println(Base.julia_cmd())'
--- --- --- `/usr/local/bin/julia -C native -J/usr/local/lib/julia/sys.so -g1`
julia -e 'using Pkg; println(Pkg.project())' 2>&1 | head -20 echo "---" julia -e 'import Pkg; Pkg.status()' 2>&1 | head -30
Pkg.API.ProjectInfo(nothing, nothing, nothing, false, Dict{String, Base.UUID}("CSV" => UUID("336ed68f-0bac-5ca0-87d4-7b16caf5d00b"), "CairoMakie" => UUID("13f3f980-e62b-5c42-98c6-ff1f3baf88f0"), "Statistics" => UUID("10745b16-79ce-11e8-11f9-7d13ad32a3b2"), "StatsBase" => UUID("2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"), "Distributions" => UUID("31c24e10-a181-5473-b8eb-7969acd0382f"), "DataFrames" => UUID("a93c6f00-e57d-5684-b7b6-d8193f3e46c0"), "JSON3" => UUID("0f8b85d8-7281-11e9-16c2-39a750bddbf1"), "Optim" => UUID("429524aa-4258-5aef-a3af-852621145aeb")), "/usr/local/share/julia/environments/v1.10/Project.toml")
---
Status `/usr/local/share/julia/environments/v1.10/Project.toml`
[336ed68f] CSV v0.10.16
[13f3f980] CairoMakie v0.15.10
[a93c6f00] DataFrames v1.8.2
[31c24e10] Distributions v0.25.125
[0f8b85d8] JSON3 v1.14.3
[429524aa] Optim v2.1.0
[2913bbd2] StatsBase v0.34.11
[10745b16] Statistics v1.10.0time julia -e 'using CairoMakie; println("loaded")' 2>&1 | tail -10loaded [stderr] real 0m6.050s user 0m5.020s sys 0m0.600s
# Try without --project flag since the packages are in the default env cd /app && timeout 180 julia analysis.jl 2>&1 | tail -30
n_obs=155, n_subj=59 Fitting base… base logL=-505.0183598711461, θ=[-5.1744416034519, 0.3674603788982618, -0.802507115192506, -0.8039720198123674, 1.032027659584984] Fitting cl_wt… cl_wt logL=-479.3799213895833, θ=[-6.0891077524840265, 0.69024823700735, 0.3443578945970128, -1.6760121188046617, -0.9038310249782254, 1.0286965490844562] Fitting full… full logL=-437.5636665083054, θ=[-5.937930920772442, 0.6137301775953106, -0.044496304637481825, -0.4736576935183165, 0.5334013370213192, -1.4842453605287869, -1.7686036566789103, 1.002580382496464] AIC: base=1020.0367197422922 cl_wt=970.7598427791665 full=891.1273330166108 LR p: 1→2=8.021361352916756e-13, 2→3=0.0 cor pop=0.8143954989436714, cor ind=0.9711268204078367 Computing SEs… Done.
ls /app/output/ echo "---" cat /app/output/model_summary.json | head -50 echo "---" cat /app/output/lrt_chain.csv echo "---" head -5 /app/output/per_subject_params.csv head -5 /app/output/obs_vs_pred.csv head -5 /app/output/residual_diagnostics.csv
lrt_chain.csv
model_summary.json
obs_vs_pred.csv
per_subject_params.csv
plot_conc_profile.png
plot_covariate_effect.png
plot_obs_vs_pred.png
plot_per_subject_fit.png
plot_residuals.png
residual_diagnostics.csv
---
{
"bic": 915.4747339519648,
"logLik": -437.5636665083054,
"fixed_effects": [
{
"term": "lCl.(Intercept)",
"t_value": -26.3422093906451,
"df": 150,
"std_error": 0.22541506798898872,
"estimate": -5.937930920772442
},
{
"term": "lCl.Wt",
"t_value": 7.469259518011839,
"df": 150,
"std_error": 0.08216747270801386,
"estimate": 0.6137301775953106
},
{
"term": "lCl.ApgarInd>= 5",
"t_value": -0.27869042972013186,
"df": 150,
"std_error": 0.1596621192990594,
"estimate": -0.044496304637481825
},
{
"term": "lV.(Intercept)",
"t_value": -6.991389565663905,
"df": 150,
"std_error": 0.06774871991750295,
"estimate": -0.4736576935183165
},
{
"term": "lV.Wt",
"t_value": 13.165238308638886,
"df": 150,
"std_error": 0.04051588923166754,
"estimate": 0.5334013370213192
}
],
"aic": 891.1273330166108,
"n_subjects": 59,
"n_obs": 155,
"random_effects_sd": {
"lCl": 0.22667333279365112,
"lV": 0.17057099828950775,
"Residual": 2.725305092766842
}
}---
step,model_name,n_params,logLik,AIC,BIC,LR_stat,LR_pvalue
1,base,5,-505.0183598711461,1020.0367197422922,1035.2538453268885,,
2,cl_wt,6,-479.3799213895833,970.7598427791665,989.0203934806821,51.27687696312569,8.021361352916756e-13
3,full,8,-437.5636665083054,891.1273330166108,915.4747339519648,83.63250976255574,0.0
---
Subject,Cl,V
1,0.005731578200767402,1.348863761123761
2,0.00543858671506775,1.2261319125512165
3,0.00802017037631752,1.6622478534084606
4,0.004052141368118357,0.9147456652899983
Subject,time,conc_obs,pred_pop,pred_ind
1,2.0,17.3,18.853622737039867,18.3772753414883
1,112.5,31.0,30.558006654354465,30.384773255181123
2,2.0,9.7,10.723806768876685,12.12554816466574
2,63.5,24.6,19.78470998592217,22.504090938256642
Subject,time,fitted_ind,resid,std_resid
1,2.0,18.3772753414883,-1.0772753414882992,-0.3952861440531801
1,112.5,30.384773255181123,0.6152267448188766,0.22574600783293336
2,2.0,12.12554816466574,-2.4255481646657415,-0.8900097721548031
2,63.5,22.504090938256642,2.0959090617433596,0.7690548362112025for f in /app/output/*.png; do
sz=$(identify -format "%wx%h" "$f" 2>/dev/null || python3 -c "from PIL import Image; img = Image.open('$f'); print(f'{img.size[0]}x{img.size[1]}')")
echo "$f: $sz"
done/app/output/plot_conc_profile.png: 2000x1280 /app/output/plot_covariate_effect.png: 2000x1200 /app/output/plot_obs_vs_pred.png: 2400x1200 /app/output/plot_per_subject_fit.png: 2400x1800 /app/output/plot_residuals.png: 2400x1200
command: view path: /app/output/plot_conc_profile.png
<system>Image resized from 2000x1280 to 1372x878 to fit display limits</system> <output_image> …[truncated]
command: view path: /app/output/plot_per_subject_fit.png
<system>Image resized from 2400x1800 to 1270x952 to fit display limits</system> <output_image> …[truncated]
command: view path: /app/output/plot_covariate_effect.png
<system>Image resized from 2000x1200 to 1400x840 to fit display limits</system> <output_image> …[truncated]
command: view path: /app/output/plot_obs_vs_pred.png
<system>Image resized from 2400x1200 to 1568x784 to fit display limits</system> <output_image> …[truncated]
command: view path: /app/output/plot_residuals.png
<system>Image resized from 2400x1200 to 1568x784 to fit display limits</system> <output_image> …[truncated]
cd /app && rm -rf output && mkdir -p output && time timeout 280 julia analysis.jl 2>&1 | tail -15
n_obs=155, n_subj=59 Fitting base… base logL=-505.0183598711461, θ=[-5.1744416034519, 0.3674603788982618, -0.802507115192506, -0.8039720198123674, 1.032027659584984] Fitting cl_wt… cl_wt logL=-479.3799213895833, θ=[-6.0891077524840265, 0.69024823700735, 0.3443578945970128, -1.6760121188046617, -0.9038310249782254, 1.0286965490844562] Fitting full… full logL=-437.5636665083054, θ=[-5.937930920772442, 0.6137301775953106, -0.044496304637481825, -0.4736576935183165, 0.5334013370213192, -1.4842453605287869, -1.7686036566789103, 1.002580382496464] AIC: base=1020.0367197422922 cl_wt=970.7598427791665 full=891.1273330166108 LR p: 1→2=8.021361352916756e-13, 2→3=0.0 cor pop=0.8143954989436714, cor ind=0.9711268204078367 Computing SEs… Done. [stderr] real 0m39.693s user 0m36.920s sys 0m0.850s
cd /app && python3 -c "
import json
with open('/app/output/model_summary.json') as f:
d = json.load(f)
print('Keys:', list(d.keys()))
print('n_obs:', d['n_obs'], type(d['n_obs']).__name__)
print('n_subjects:', d['n_subjects'], type(d['n_subjects']).__name__)
print('logLik:', d['logLik'])
print('aic:', d['aic'], 'bic:', d['bic'])
print('RE SDs:', d['random_effects_sd'])
print('Fixed effects:')
for fe in d['fixed_effects']:
print(f' {fe[\"term\"]:30s} est={fe[\"estimate\"]:+.4f} se={fe[\"std_error\"]:.4f} t={fe[\"t_value\"]:+.3f} df={fe[\"df\"]}')
"Keys: ['bic', 'logLik', 'fixed_effects', 'aic', 'n_subjects', 'n_obs', 'random_effects_sd']
n_obs: 155 int
n_subjects: 59 int
logLik: -437.5636665083054
aic: 891.1273330166108 bic: 915.4747339519648
RE SDs: {'lCl': 0.22667333279365112, 'lV': 0.17057099828950775, 'Residual': 2.725305092766842}
Fixed effects:
lCl.(Intercept) est=-5.9379 se=0.2254 t=-26.342 df=150
lCl.Wt est=+0.6137 se=0.0822 t=+7.469 df=150
lCl.ApgarInd>= 5 est=-0.0445 se=0.1597 t=-0.279 df=150
lV.(Intercept) est=-0.4737 se=0.0677 t=-6.991 df=150
lV.Wt est=+0.5334 se=0.0405 t=+13.165 df=150cd /app && python3 -c "
import pandas as pd
df = pd.read_csv('/app/output/per_subject_params.csv')
print('columns:', list(df.columns))
print('shape:', df.shape)
print(df.head())
print()
print('All Cl>0:', (df.Cl > 0).all(), 'All V>0:', (df.V > 0).all())
print('Sorted by Subject:', df.Subject.is_monotonic_increasing)
"
echo
python3 -c "
import pandas as pd
df = pd.read_csv('/app/output/obs_vs_pred.csv')
print('columns:', list(df.columns))
print('shape:', df.shape)
print('Sorted by Subject,time:', (df.sort_values(['Subject','time']).values == df.values).all())
from numpy import corrcoef
print('cor pop:', corrcoef(df.conc_obs, df.pred_pop)[0,1])
print('cor ind:', corrcoef(df.conc_obs, df.pred_ind)[0,1])
"
echo
python3 -c "
import pandas as pd
df = pd.read_csv('/app/output/residual_diagnostics.csv')
print('columns:', list(df.columns))
print('shape:', df.shape)
print(df.head())
"
echo
python3 -c "
import pandas as pd
df = pd.read_csv('/app/output/lrt_chain.csv')
print(df)
"columns: ['Subject', 'Cl', 'V'] shape: (59, 3) Subject Cl V 0 1 0.005732 1.348864 1 2 0.005439 1.226132 2 3 0.008020 1.662248 3 4 0.004052 0.914746 4 5 0.007399 1.757434 All Cl>0: True All V>0: True Sorted by Subject: True columns: ['Subject', 'time', 'conc_obs', 'pred_pop', 'pred_ind'] shape: (155, 5) Sorted by Subject,time: True cor pop: 0.8143954989436711 cor ind: 0.9711268204078357 columns: ['Subject', 'time', 'fitted_ind', 'resid', 'std_resid'] shape: (155, 5) Subject time fitted_ind resid std_resid 0 1 2.0 18.377275 -1.077275 -0.395286 1 1 112.5 30.384773 0.615227 0.225746 2 2 2.0 12.125548 -2.425548 -0.890010 3 2 63.5 22.504091 2.095909 0.769055 4 2 135.5 31.884487 1.115513 0.409317 step model_name n_params ... BIC LR_stat LR_pvalue 0 1 base 5 ... 1035.253845 NaN NaN 1 2 cl_wt 6 ... 989.020393 51.276877 8.021361e-13 2 3 full 8 ... 915.474734 83.632510 0.000000e+00 [3 rows x 8 columns]
cat /app/output/lrt_chain.csv
step,model_name,n_params,logLik,AIC,BIC,LR_stat,LR_pvalue 1,base,5,-505.0183598711461,1020.0367197422922,1035.2538453268885,, 2,cl_wt,6,-479.3799213895833,970.7598427791665,989.0203934806821,51.27687696312569,8.021361352916756e-13 3,full,8,-437.5636665083054,891.1273330166108,915.4747339519648,83.63250976255574,0.0
julia -e 'using Distributions;
println("1-cdf: ", 1 - cdf(Chisq(2), 83.6))
println("ccdf: ", ccdf(Chisq(2), 83.6))'1-cdf: 0.0 ccdf: 7.022482351711484e-19
command: str_replace
path: /app/analysis.jl
old_str: lr12 = 2*(logL_cw - logL_b); lr23 = 2*(logL_f - logL_cw)
p12 = 1 - cdf(Chisq(1), max(lr12, 0.0))
p23 = 1 - cdf(Chisq(2), max(lr23, 0.0))
new_str: lr12 = 2*(logL_cw - logL_b); lr23 = 2*(logL_f - logL_cw)
p12 = ccdf(Chisq(1), max(lr12, 0.0))
p23 = ccdf(Chisq(2), max(lr23, 0.0))The file /app/analysis.jl has been edited successfully.
cd /app && rm -rf output && mkdir -p output && time timeout 280 julia analysis.jl 2>&1 | tail -10
base logL=-505.0183598711461, θ=[-5.1744416034519, 0.3674603788982618, -0.802507115192506, -0.8039720198123674, 1.032027659584984] Fitting cl_wt… cl_wt logL=-479.3799213895833, θ=[-6.0891077524840265, 0.69024823700735, 0.3443578945970128, -1.6760121188046617, -0.9038310249782254, 1.0286965490844562] Fitting full… full logL=-437.5636665083054, θ=[-5.937930920772442, 0.6137301775953106, -0.044496304637481825, -0.4736576935183165, 0.5334013370213192, -1.4842453605287869, -1.7686036566789103, 1.002580382496464] AIC: base=1020.0367197422922 cl_wt=970.7598427791665 full=891.1273330166108 LR p: 1→2=8.021493759586122e-13, 2→3=6.9092554726142315e-19 cor pop=0.8143954989436714, cor ind=0.9711268204078367 Computing SEs… Done. [stderr] real 0m41.175s user 0m37.830s sys 0m1.330s
cat /app/output/lrt_chain.csv echo ls -la /app/output/
step,model_name,n_params,logLik,AIC,BIC,LR_stat,LR_pvalue 1,base,5,-505.0183598711461,1020.0367197422922,1035.2538453268885,, 2,cl_wt,6,-479.3799213895833,970.7598427791665,989.0203934806821,51.27687696312569,8.021493759586122e-13 3,full,8,-437.5636665083054,891.1273330166108,915.4747339519648,83.63250976255574,6.9092554726142315e-19 total 1343 drwxr-sr-x 2 h2tagent h2tagent 4096 May 31 10:22 . drwxrws--- 3 h2tagent h2tagent 4096 May 31 10:21 .. -rw-r--r-- 1 h2tagent h2tagent 336 May 31 10:22 lrt_chain.csv -rw-r--r-- 1 h2tagent h2tagent 1367 May 31 10:22 model_summary.json -rw-r--r-- 1 h2tagent h2tagent 7748 May 31 10:22 obs_vs_pred.csv -rw-r--r-- 1 h2tagent h2tagent 2532 May 31 10:22 per_subject_params.csv -rw-r--r-- 1 h2tagent h2tagent 397087 May 31 10:22 plot_conc_profile.png -rw-r--r-- 1 h2tagent h2tagent 110543 May 31 10:22 plot_covariate_effect.png -rw-r--r-- 1 h2tagent h2tagent 187247 May 31 10:22 plot_obs_vs_pred.png -rw-r--r-- 1 h2tagent h2tagent 496301 May 31 10:22 plot_per_subject_fit.png -rw-r--r-- 1 h2tagent h2tagent 151963 May 31 10:22 plot_residuals.png -rw-r--r-- 1 h2tagent h2tagent 10192 May 31 10:22 residual_diagnostics.csv
python3 <<'EOF'
import json, pandas as pd
from numpy import corrcoef
# 1. Check model_summary.json
with open('/app/output/model_summary.json') as f:
d = json.load(f)
required = ['n_obs', 'n_subjects', 'fixed_effects', 'random_effects_sd',
'logLik', 'aic', 'bic']
for k in required:
assert k in d, f"missing {k}"
assert isinstance(d['n_obs'], int)
assert isinstance(d['n_subjects'], int)
assert len(d['fixed_effects']) == 5
expected_terms = ["lCl.(Intercept)", "lCl.Wt", "lCl.ApgarInd>= 5",
"lV.(Intercept)", "lV.Wt"]
got_terms = [fe['term'] for fe in d['fixed_effects']]
assert got_terms == expected_terms, f"term order mismatch: {got_terms}"
for fe in d['fixed_effects']:
for k in ['term','estimate','std_error','t_value','df']:
assert k in fe
assert set(d['random_effects_sd'].keys()) == {'lCl','lV','Residual'}
for v in d['random_effects_sd'].values():
assert v > 0
print("model_summary.json: PASS")
# 2. Check lrt_chain.csv
df = pd.read_csv('/app/output/lrt_chain.csv')
assert list(df.columns) == ['step','model_name','n_params','logLik','AIC','BIC','LR_stat','LR_pvalue']
assert list(df.step) == [1,2,3]
assert list(df.model_name) == ['base','cl_wt','full']
# AIC strictly decreasing
aic = df.AIC.tolist()
assert aic[0] > aic[1] > aic[2], f"AIC not strictly decreasing: {aic}"
# LR p-values for steps 2,3 both < 1e-6
assert df.loc[1,'LR_pvalue'] < 1e-6, f"1→2 p={df.loc[1,'LR_pvalue']}"
assert df.loc[2,'LR_pvalue'] < 1e-6, f"2→3 p={df.loc[2,'LR_pvalue']}"
# Step 1 LR missing
import math
assert math.isnan(df.loc[0,'LR_stat']) and math.isnan(df.loc[0,'LR_pvalue'])
print("lrt_chain.csv: PASS")
# 3. per_subject_params.csv
df = pd.read_csv('/app/output/per_subject_params.csv')
assert list(df.columns) == ['Subject','Cl','V']
assert (df.Cl > 0).all() and (df.V > 0).all()
assert df.Subject.is_monotonic_increasing
print("per_subject_params.csv: PASS")
# 4. obs_vs_pred.csv
df = pd.read_csv('/app/output/obs_vs_pred.csv')
assert list(df.columns) == ['Subject','time','conc_obs','pred_pop','pred_ind']
sorted_df = df.sort_values(['Subject','time']).reset_index(drop=True)
assert (df.reset_index(drop=True).values == sorted_df.values).all()
cp = corrcoef(df.conc_obs, df.pred_pop)[0,1]
ci = corrcoef(df.conc_obs, df.pred_ind)[0,1]
assert cp > 0.55, f"cor pop = {cp}"
assert ci > 0.75, f"cor ind = {ci}"
print(f"obs_vs_pred.csv: PASS (cor_pop={cp:.3f}, cor_ind={ci:.3f})")
# 5. residual_diagnostics.csv
df = pd.read_csv('/app/output/residual_diagnostics.csv')
assert list(df.columns) == ['Subject','time','fitted_ind','resid','std_resid']
sorted_df = df.sort_values(['Subject','time']).reset_index(drop=True)
assert (df.reset_index(drop=True).values == sorted_df.values).all()
print("residual_diagnostics.csv: PASS")
# 6. Check plots exist with correct sizes
import os
from PIL import Image
for name in ['plot_conc_profile','plot_per_subject_fit','plot_covariate_effect',
'plot_obs_vs_pred','plot_residuals']:
p = f'/app/output/{name}.png'
assert os.path.exists(p), f"missing {p}"
img = Image.open(p)
w, h = img.size
assert w >= 900 and h >= 550, f"{p}: {w}x{h}"
print(f"{name}.png: {w}x{h} PASS")
print("\nALL CHECKS PASS")
EOFmodel_summary.json: PASS lrt_chain.csv: PASS per_subject_params.csv: PASS obs_vs_pred.csv: PASS (cor_pop=0.814, cor_ind=0.971) residual_diagnostics.csv: PASS plot_conc_profile.png: 2000x1280 PASS plot_per_subject_fit.png: 2400x1800 PASS plot_covariate_effect.png: 2000x1200 PASS plot_obs_vs_pred.png: 2400x1200 PASS plot_residuals.png: 2400x1200 PASS ALL CHECKS PASS
grep -n "min(9," /app/analysis.jl
373: chosen = sort(ordered[1:min(9, length(ordered))], by=s -> s.id)
sed -n '370,410p' /app/analysis.jl
# per-subject fit
let
ordered = sort(subjects, by=s -> -length(s.samp_t))
chosen = sort(ordered[1:min(9, length(ordered))], by=s -> s.id)
fig = Figure(size=(1200, 900))
Label(fig[0, :],
"Per-subject observed points with population (red) and individual (blue) predictions",
fontsize=16)
for (k, s) in enumerate(chosen)
r = div(k-1, 3) + 1; c = mod(k-1, 3) + 1
ax = Axis(fig[r, c], xlabel="Time (h)", ylabel="Conc (mg/L)",
title="Subject $(s.id)")
lCl, lV = compute_lCl_lV(p_final, s)
η̂ = ebs[s.id]
Cl_pop = exp(lCl); V_pop = exp(lV)
Cl_ind = exp(lCl + η̂[1]); V_ind = exp(lV + η̂[2])
tmax = maximum([s.samp_t; s.dose_t]) + 5
tg = range(0.0, tmax; length=400)
yp = [predict_conc(t, Cl_pop, V_pop, s.dose_t, s.dose_amt) for t in tg]
yi = [predict_conc(t, Cl_ind, V_ind, s.dose_t, s.dose_amt) for t in tg]
lines!(ax, tg, yp; color=:red, linewidth=1.6,
label=(k == 1 ? "Population" : nothing))
lines!(ax, tg, yi; color=:blue, linewidth=1.6,
label=(k == 1 ? "Individual" : nothing))
scatter!(ax, s.samp_t, s.samp_y; color=:black, markersize=7,
label=(k == 1 ? "Observed" : nothing))
if k == 1
axislegend(ax, position=:rt)
end
end
save(joinpath(out_dir, "plot_per_subject_fit.png"), fig)
end
# covariate effect
let
fig = Figure(size=(1000, 600))
ax = Axis(fig[1, 1], xlabel="Birth weight (kg)",
ylabel="Subject-level lCl",
title="Individual lCl (EB) vs birth weight, colored by Apgar")
wts_hi = Float64[]; lcl_hi = Float64[]
wts_lo = Float64[]; lcl_lo = Float64[]grep -n "isempty\|length(s.samp" /app/analysis.jl | head -20
101: n = length(s.samp_t) 357: if s.ApgarInd == key && !isempty(s.samp_t) 372: ordered = sort(subjects, by=s -> -length(s.samp_t))
command: str_replace
path: /app/analysis.jl
old_str: tmax = maximum([s.samp_t; s.dose_t]) + 5
new_str: allt = [s.samp_t; s.dose_t]
tmax = isempty(allt) ? 24.0 : maximum(allt) + 5The file /app/analysis.jl has been edited successfully.
cd /app && rm -rf output && mkdir -p output && time timeout 180 julia analysis.jl 2>&1 | tail -10
base logL=-505.0183598711461, θ=[-5.1744416034519, 0.3674603788982618, -0.802507115192506, -0.8039720198123674, 1.032027659584984] Fitting cl_wt… cl_wt logL=-479.3799213895833, θ=[-6.0891077524840265, 0.69024823700735, 0.3443578945970128, -1.6760121188046617, -0.9038310249782254, 1.0286965490844562] Fitting full… full logL=-437.5636665083054, θ=[-5.937930920772442, 0.6137301775953106, -0.044496304637481825, -0.4736576935183165, 0.5334013370213192, -1.4842453605287869, -1.7686036566789103, 1.002580382496464] AIC: base=1020.0367197422922 cl_wt=970.7598427791665 full=891.1273330166108 LR p: 1→2=8.021493759586122e-13, 2→3=6.9092554726142315e-19 cor pop=0.8143954989436714, cor ind=0.9711268204078367 Computing SEs… Done. [stderr] real 0m42.085s user 0m38.870s sys 0m1.180s
# Quick check - try perturbing the dataset slightly and see if fits still work
cp /app/Phenobarb.csv /tmp/test_data.csv
# Shuffle rows and scale some values modestly
python3 <<'EOF'
import pandas as pd, numpy as np
df = pd.read_csv('/tmp/test_data.csv')
np.random.seed(42)
# Multiply concentrations by small noise
mask = df.conc.notna()
df.loc[mask, 'conc'] = df.loc[mask, 'conc'] * np.random.uniform(0.9, 1.1, mask.sum())
df.to_csv('/tmp/test_data.csv', index=False)
print(f"Wrote {len(df)} rows, {df.Subject.nunique()} subjects")
EOF
cd /app && PHENOBARB_DATA_PATH=/tmp/test_data.csv PHENOBARB_OUTPUT_DIR=/tmp/test_out time timeout 120 julia analysis.jl 2>&1 | tail -10Wrote 744 rows, 59 subjects bash: line 2129: time: command not found
cd /app && mkdir -p /tmp/test_out && rm -f /tmp/test_out/* PHENOBARB_DATA_PATH=/tmp/test_data.csv PHENOBARB_OUTPUT_DIR=/tmp/test_out timeout 120 julia analysis.jl 2>&1 | tail -10
base logL=-514.3114881716132, θ=[-5.162335547310126, 0.3749668136207289, -0.8846459550555295, -0.7685088049884892, 1.1767655888155693] Fitting cl_wt… cl_wt logL=-491.35536225729896, θ=[-6.029122023715867, 0.6664468178704971, 0.34883275261794655, -1.8260460528336127, -0.8790753161225556, 1.1853932345817864] Fitting full… full logL=-452.192764434794, θ=[-5.826282500624983, 0.5729512073020642, -0.07802150992438209, -0.48762817314547335, 0.5443571243989244, -1.5568101967838246, -1.7173772275428445, 1.1551610021214789] AIC: base=1038.6229763432264 cl_wt=994.7107245145979 full=920.385528869588 LR p: 1→2=1.2367008886887802e-11, 2→3=9.815216164597832e-18 cor pop=0.7932981249566103, cor ind=0.9558092348685295 Computing SEs… Done.
# More substantial perturbation: remove some subjects and add noise
python3 <<'EOF'
import pandas as pd, numpy as np
df = pd.read_csv('/app/Phenobarb.csv')
np.random.seed(7)
# Remove 10 random subjects
subjects = sorted(df.Subject.unique())
keep = np.random.choice(subjects, size=len(subjects)-10, replace=False)
df = df[df.Subject.isin(keep)].reset_index(drop=True)
# Noise on concentrations
mask = df.conc.notna()
df.loc[mask, 'conc'] = df.loc[mask, 'conc'] * np.random.uniform(0.85, 1.15, mask.sum())
df.to_csv('/tmp/test_data2.csv', index=False)
print(f"Wrote {len(df)} rows, {df.Subject.nunique()} subjects, {df.conc.notna().sum()} conc obs")
EOF
cd /app && mkdir -p /tmp/test_out2 && rm -f /tmp/test_out2/*
PHENOBARB_DATA_PATH=/tmp/test_data2.csv PHENOBARB_OUTPUT_DIR=/tmp/test_out2 timeout 120 julia analysis.jl 2>&1 | tail -8Wrote 626 rows, 49 subjects, 130 conc obs cl_wt logL=-417.63026827699133, θ=[-6.11183413801462, 0.7722384544084155, 0.3144721590941398, -2.5023916483560487, -1.0060893760301333, 1.3814120056216328] Fitting full… full logL=-383.5229207880988, θ=[-6.049235533164087, 0.6357099086166081, 0.1385154492638951, -0.5711525691322201, 0.580156136737197, -2.0103318105834087, -1.8430462396493918, 1.307452387405179] AIC: base=879.9245574933991 cl_wt=847.2605365539827 full=783.0458415761976 LR p: 1→2=3.9180640695720574e-9, 2→3=1.5394556901879137e-15 cor pop=0.8124350620286483, cor ind=0.939115861420384 Computing SEs… Done.
cd /app && rm -rf output && mkdir -p output && timeout 180 julia analysis.jl 2>&1 | tail -10 echo "---" ls /app/output/ | sort echo "---" wc -l /app/output/*.csv
base logL=-505.0183598711461, θ=[-5.1744416034519, 0.3674603788982618, -0.802507115192506, -0.8039720198123674, 1.032027659584984]
Fitting cl_wt…
cl_wt logL=-479.3799213895833, θ=[-6.0891077524840265, 0.69024823700735, 0.3443578945970128, -1.6760121188046617, -0.9038310249782254, 1.0286965490844562]
Fitting full…
full logL=-437.5636665083054, θ=[-5.937930920772442, 0.6137301775953106, -0.044496304637481825, -0.4736576935183165, 0.5334013370213192, -1.4842453605287869, -1.7686036566789103, 1.002580382496464]
AIC: base=1020.0367197422922 cl_wt=970.7598427791665 full=891.1273330166108
LR p: 1→2=8.021493759586122e-13, 2→3=6.9092554726142315e-19
cor pop=0.8143954989436714, cor ind=0.9711268204078367
Computing SEs…
Done.
---
lrt_chain.csv
model_summary.json
obs_vs_pred.csv
per_subject_params.csv
plot_conc_profile.png
plot_covariate_effect.png
plot_obs_vs_pred.png
plot_per_subject_fit.png
plot_residuals.png
residual_diagnostics.csv
---
4 /app/output/lrt_chain.csv
156 /app/output/obs_vs_pred.csv
60 /app/output/per_subject_params.csv
156 /app/output/residual_diagnostics.csv
376 totalNo file changes, the agent left the workspace untouched.
# SyncVals normalized verifier summary
# task: neonatal-drug-exposure-nlme
# attempt: 6 status: completed raw_score: 0.0
# reward rule: binary: score 0.0 != 1.0
# => reward = 0.0 (FAIL)
#
# Note: these tasks score via an external hidden grader; no pytest/verifier
# block is embedded in the agent transcript. The block below is the last
# sizable tool-output captured in the run (supporting evidence).
----------------------------------------------------------------------
base logL=-505.0183598711461, θ=[-5.1744416034519, 0.3674603788982618, -0.802507115192506, -0.8039720198123674, 1.032027659584984]
Fitting cl_wt…
cl_wt logL=-479.3799213895833, θ=[-6.0891077524840265, 0.69024823700735, 0.3443578945970128, -1.6760121188046617, -0.9038310249782254, 1.0286965490844562]
Fitting full…
full logL=-437.5636665083054, θ=[-5.937930920772442, 0.6137301775953106, -0.044496304637481825, -0.4736576935183165, 0.5334013370213192, -1.4842453605287869, -1.7686036566789103, 1.002580382496464]
AIC: base=1020.0367197422922 cl_wt=970.7598427791665 full=891.1273330166108
LR p: 1→2=8.021493759586122e-13, 2→3=6.9092554726142315e-19
cor pop=0.8143954989436714, cor ind=0.9711268204078367
Computing SEs…
Done.
---
lrt_chain.csv
model_summary.json
obs_vs_pred.csv
per_subject_params.csv
plot_conc_profile.png
plot_covariate_effect.png
plot_obs_vs_pred.png
plot_per_subject_fit.png
plot_residuals.png
residual_diagnostics.csv
---
4 /app/output/lrt_chain.csv
156 /app/output/obs_vs_pred.csv
60 /app/output/per_subject_params.csv
156 /app/output/residual_diagnostics.csv
376 totalReproduce this trial: git checkout 2f94510 && PYTHONPATH=src python3 scripts/build_site.py , then open trial/trial_16b8cd529a90421a. Re-running the agent live requires EVAL_PLATFORM_ENABLE_OAUTH_SMOKE=1 and is non-deterministic.
Trial trial_16b8cd529a90421a · verifier authoritative; classifier explanatory.