Custom Correlation Models

The Getting started example uses the built-in AffineTimeLinearModel, which maps dissolution to absorption through affine time-scaling and a linear fraction map. This guide demonstrates how to define a custom AbstractCorrelationModel by implementing the correlation interface — useful when the dissolution–absorption relationship is non-linear or requires domain-specific parameterization.

Alpha feature

The flexible correlation interface is under active development. The API may change.

Simulated data

first(pk_df, 8)
8×4 DataFrame
Rowidformulationtimeconc
Int64StringFloat64Float64
11FormA0.00.0
21FormA0.4790170.252104
31FormA0.9406820.766301
41FormA1.51661.48553
51FormA2.06182.15264
61FormA2.501082.64725
71FormA3.053583.17229
81FormA3.459243.49077

Defining a custom correlation model

To use the flexible interface, define a subtype of AbstractCorrelationModel whose fields are the model parameters (with a no-argument constructor), and implement four methods:

  1. default_parameter_specs — default ParameterSpec list; names must match the struct's fields, in field order
  2. correlation_vivo_to_vitro_time — map in vivo time to in vitro time
  3. correlation_vitro_to_vivo_time — the inverse time map
  4. correlation_dissolution_to_absorption — map fraction dissolved to fraction absorbed

A model whose parameters include a time (a shift or a lag) also declares them through time_parameters, so the fit converts them between the data's time unit and magnitudes and scales their restarts to the observed time span.

This example replicates the built-in affine model to demonstrate the interface:

struct DemoAffineModel <: AbstractCorrelationModel
    abs_scale::Float64
    abs_intercept::Float64
    time_scale::Float64
    time_shift::Float64
end
DemoAffineModel() = DemoAffineModel(1.0, 0.0, 1.0, 0.0)

InVitroInVivoCorrelation.default_parameter_specs(::DemoAffineModel) = [
    ParameterSpec(:abs_scale, 1.0),
    ParameterSpec(:abs_intercept, 0.0),
    ParameterSpec(:time_scale, 1.0, 1e-6, 100.0),
    ParameterSpec(:time_shift, 0.0),
]
InVitroInVivoCorrelation.time_parameters(::DemoAffineModel) = [:time_shift]

function InVitroInVivoCorrelation.correlation_vivo_to_vitro_time(m::DemoAffineModel, t_vivo)
    t = Float64.(collect(t_vivo))
    return m.time_scale .* t .+ m.time_shift
end

function InVitroInVivoCorrelation.correlation_vitro_to_vivo_time(m::DemoAffineModel, t_vitro)
    t = Float64.(collect(t_vitro))
    return (t .- m.time_shift) ./ m.time_scale
end

function InVitroInVivoCorrelation.correlation_dissolution_to_absorption(m::DemoAffineModel, frac_diss)
    f = Float64.(collect(frac_diss))
    return m.abs_scale .* f .+ m.abs_intercept
end

correlation_model = DemoAffineModel()
parameter_specs = default_parameter_specs(correlation_model)
4-element Vector{InVitroInVivoCorrelation.ParameterSpec{Float64}}:
 ParameterSpec(:abs_scale, 1.0, -Inf, Inf)
 ParameterSpec(:abs_intercept, 0.0, -Inf, Inf)
 ParameterSpec(:time_scale, 1.0, 1.0e-6, 100.0)
 ParameterSpec(:time_shift, 0.0, -Inf, Inf)

Running the pipeline

Pass the correlation_model and parameter_specs to run_ivivc:

result = run_ivivc(
    pk_df,
    diss_df;
    fast_formulation = "IV",
    validation_formulation = "FormB",
    times = collect(range(0.0, 48.0, length = 101)),
    correlation_model = correlation_model,
    parameter_specs = parameter_specs,
);

Model parameters

coeftable(result.model)
4×3 DataFrame
Rowparameterconstantestimate
SymbolBoolFloat64
1abs_scalefalse0.9896
2abs_interceptfalse0.00185422
3time_scalefalse0.841655
4time_shiftfalse-0.0835769

Plots

concentration_profiles(result)
Example block output
dissolution_profiles(result)
Example block output
absorption_profiles(result)
Example block output
levy_plot(result)
Example block output
ivivc_correlation(result)
Example block output