Skip to main content
Skip table of contents

Example: Implementing a custom cost function

Overview

This example demonstrates how to implement a custom penalty-based cost function for mlxModelFinder, inspired by the approach described in the following paper:

Richardson, S., Irurzun Arana, I., Nowojewski, A. et al. A machine learning approach to population pharmacokinetic modelling automation. Commun Med 5, 327 (2025). https://doi.org/10.1038/s43856-025-01054-8

This cost function combines the model’s statistical fit (AIC) with penalties on various quality metrics such as relative standard errors (RSEs), omega values, shrinkage, and the condition number. It allows model selection to account for both goodness-of-fit and model stability.

Penalty Structure

Each model metric is evaluated against a set of thresholds, with penalties increasing as the metric deviates from acceptable ranges.
The function applies piecewise linear penalties defined as follows:

Parameter

Estimated value

Threshold

Penalty

Fixed effects

Relative error

10%

0

30%

20

75%

100

1000%

100000

Random effects

Relative error

20%

0

40%

30

65%

100

1000%

100000

Random effects

Value (CV%)

0 (0 CV%)

1000000

0.001 (3 CV%)

1000

0.01 (10 CV%)

0

1 (131 CV%)

0

1.65 (205 CV%)

100

2.3 (300 CV%)

500

3 (437 CV%)

10000

3.5 (567 CV%)

1000000

Random effects

Shrinkage

40%

0

100%

200

Condition number

Condition number

500

0

1000

100

10000

1000

1000000

10000000

Implementation in R

Below is a sample implementation of the penalized cost function.
It reads Monolix output files, extracts relevant metrics, computes penalties via linear interpolation, and returns the penalized AIC value.

R
calculate_penalized_aic <- function() {
    
    # --- 1. Setup and Helper Functions ---
    # Helper for piecewise-linear interpolation
    # It calculates a y-value for a given x based on a set of (xp, yp) points.
    piecewise_linear_interp <- function(x, xp, yp) {
        stopifnot(length(xp) == length(yp), length(xp) >= 2)

        if (is.na(x)) return(0)
        if (x <= xp[1]) return(yp[1])
        if (x >= tail(xp, 1)) return(tail(yp, 1))

        i <- findInterval(x, xp)
        x0 <- xp[i]; x1 <- xp[i + 1]
        y0 <- yp[i]; y1 <- yp[i + 1]
        
        y0 + (y1 - y0) * (x - x0) / (x1 - x0)
    }
    
    # --- 2. Penalty Calculation Definitions ---
    # These functions define the penalty curves for different metrics.
    pen_theta_rse <- function(rse_pct) {
        piecewise_linear_interp(rse_pct,
                                xp = c(10, 30, 75, 1000),
                                yp = c(0, 20, 100, 100000))
    }
    pen_omega_rse <- function(rse_pct) {
        piecewise_linear_interp(rse_pct,
                                xp = c(20, 40, 65, 1000),
                                yp = c(0, 30, 100, 100000))
    }
    pen_omega_value <- function(omega_sd_val) {
        omega_var_val <- omega_sd_val^2
        xp <- c(0.0000, 0.0010, 0.0100, 1.0000, 1.6500, 2.3000, 3.0000, 3.5000)
        yp <- c(1e6,    1e3,    0,      0,      100,    500,    1e4,    1e6)
        piecewise_linear_interp(omega_var_val, xp, yp)
    }
    pen_omega_shrink <- function(shr_pct) {
        piecewise_linear_interp(shr_pct, xp = c(40, 100), yp = c(0, 200))
    }
    pen_condition_number <- function(cond_num) {
        piecewise_linear_interp(cond_num,
                                xp = c(500, 1000, 10000, 1e6),
                                yp = c(0, 100, 1000, 1e7))
    }
    
    # --- 3. Extract Metrics from Monolix Project ---
    proj_dir <- getProjectSettings()$directory

    AIC <- getEstimatedLogLikelihood()$importanceSampling[["AIC"]]
    if (is.null(AIC) || is.na(AIC)) {
        warning("AIC from Importance Sampling not found. Using AIC from Linearization.")
        AIC <- getEstimatedLogLikelihood()$linearization[["AIC"]]
        if (is.null(AIC)) stop("Could not retrieve AIC value.")
    }
    
    pop_params <- getEstimatedPopulationParameters()
    std_errs <- getEstimatedStandardErrors()$stochasticApproximation
    
    is_theta <- grepl("(_pop|beta_)", names(pop_params)) & !grepl("omega", names(pop_params))
    is_omega <- grepl("^omega_", names(pop_params)) & !grepl("corr|cov", names(pop_params))
    
    theta_rse <- std_errs[is_theta, "rse"]
    omega_rse <- std_errs[is_omega, "rse"]
    omega_values <- pop_params[is_omega]
    
    shrinkage_file <- file.path(proj_dir, "individualParameters", "shrinkage.txt")
    shrinkage <- if (file.exists(shrinkage_file)) {
        read.csv(shrinkage_file)$shrinkage_mode
    } else {
        warning("Shrinkage file not found. Skipping shrinkage penalty.")
        numeric(0)
    }
    
    fim_file <- file.path(proj_dir, "FisherInformation", "covarianceEstimatesSA.txt")
    condition_number <- if (file.exists(fim_file)) {
        fim <- as.matrix(read.csv(fim_file, header = FALSE, row.names = 1))
        eigen_values <- eigen(fim, only.values = TRUE)$values
        max(eigen_values) / min(eigen_values)
    } else {
        warning("FIM file not found. Skipping condition number penalty.")
        NA
    }
    
    # --- 4. Compute Penalties ---
    p_theta_rse <- sum(sapply(theta_rse, pen_theta_rse))
    p_omega_rse <- sum(sapply(omega_rse, pen_omega_rse))
    p_omega_val <- sum(sapply(omega_values, pen_omega_value))
    p_shrinkage <- sum(sapply(shrinkage, pen_omega_shrink))
    p_cond_num  <- pen_condition_number(condition_number)
    
    # --- 5. Aggregate and Return Results ---
    penalty_breakdown <- c(
        THETA_RSE        = p_theta_rse,
        OMEGA_RSE        = p_omega_rse,
        OMEGA_value      = p_omega_val,
        OMEGA_shrinkage  = p_shrinkage,
        Condition_number = p_cond_num
    )
    
    total_penalty <- sum(penalty_breakdown, na.rm = TRUE)
    penalized_AIC <- AIC + total_penalty
    
    penalized_AIC
}

Usage

Provide this function as an argument to the findModel function:

R
res <- findModel(
  project = "theophylline_project.mlxtran",
  library = "pk",
  requiredFilters = list(administration = "oral", parametrization = "clearance"),
  # optional: filters / algorithm / settings ...
  metric_func = calculate_penalized_aic
)
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.