# Nonmem control file to PoPy Fit Script

The Nonmem control file has the same purpose as the PoPy Fit Script, i.e. to estimate the fixed effects given a PK/PD model and a data file. Table 53 lists the equivalent script file sections.

Table 53 Nonmem to PoPy Fitting
Nonmem | PoPy | Comment
--- | --- | ---
$PROBLEM | DESCRIPTION | Model Description
$DATA | FILE_PATHS | Input data file path
$INPUT | DATA_FIELDS | Data header information
$SUBROUTINES | ODE_SOLVER | ordinary differential equation method
$MODEL | N/A | Number of compartments
$THETA | EFFECTS | Main fixed effects
$OMEGA | EFFECTS | Variance of random effects
$SIGMA | EFFECTS | Variance of measurement noise
$PK | MODEL_PARAMS | Model Parameters
$DES | DERIVATIVES | ordinary differential equation system
$ERROR | PREDICTIONS | Likelihood model
$ESTIMATION | FIT_METHODS | Fitting algorithms

## $PROBLEM

Nonmem:-

    $PROBLEM My pkpd model

to PoPy:-

    DESCRIPTION: {title: My pkpd model}

Note in PoPy you can optionally add additional fields ‘name’, ‘author’, ‘abstract’ and ‘keywords’, see DESCRIPTION section for examples.

## $DATA

Nonmem:-

    $DATA my_nm_data.csv

to PoPy:-

    FILE_PATHS: {input_data_file: my_popy_data.csv}

Here the file ‘my_nm_data.csv’ is assumed to be in the same directory as the Nonmem script file. Similarly ‘my_popy_data.csv’ will need to be in the same directory as the PoPy script.

Note you will need to convert the data file to a valid PoPy data file, see Nonmem Data to PoPy Data File.

## $INPUT

Nonmem:-

    $INPUT EVID ID TIME CMT AMT DV MDV

to PoPy:-

    DATA_FIELDS: {type_field: TYPE, id_field: ID, time_field: TIME}

Note that the Nonmem ‘$INPUT’ section redefines the header file for the data file. PoPy simply uses the header supplied in the .csv data file.

In PoPy you only need to specify the following fields in the DATA_FIELDS section:-

- type_field - equivalent to Nonmem EVID
- id_field - same as Nonmem ID
- time_field - same as Nonmem TIME

If you miss out the DATA_FIELDS section completely. Then you just need the default field names of ‘TYPE’, ‘ID’ and ‘TIME’ to be present in your data file.

See Nonmem Data to PoPy Data File for a more detailed discussion of the data format differences between Nonmem and PoPy.

## $SUBROUTINES

### ADVAN13 Example

Nonmem:-

    $SUBROUTINES ADVAN13 TOL=6

to PoPy:-

    ODE_SOLVER: {CPPLSODA: {rtol: 1e-06}}

In the Nonmem script ‘ADVAN13’ specifies the LSODA solver which is used to compute numerical solutions for the ordinary differential equations specified in the Nonmem $DES block.

The equivalent of this in PoPy is to specify ‘CPPLSODA’ in the ODE_SOLVER section, which is a Python wrapper around the LSODA solver. The ‘CPPLSODA’ parameters are as follows:-

- rtol - relative tolerance of LSODA solver, equivalent of TOL in Nonmem ($SUBROUTINES section)
- atol - additive tolerance of LSODA solver, equivalent of ATOL in Nonmem ($ESTIMATION section)
- max_nsteps - maximum number of steps allowed for LSODA solver, an exposed parameter in PoPy with a default value of ‘1e7’. In Nonmem this is fixed to ‘750’ and not configurable.

See ODE_SOLVER section for more information on the ‘CPPLSODA’ solver and other solvers available in PoPy.

### ADVAN 1,2,3,4,11,12 Example

Note if an analytic ‘ADVAN’ model is used in Nonmem for example:-

    $SUBROUTINES  ADVAN2 TRANS2

Then this is converted to PoPy in a slightly different way, the ODE_SOLVER is as follows:-

    ODE_SOLVER: {ANALYTIC:{}}

And the DERIVATIVES section is as follows:-

    DERIVATIVES: |
        s[ABS,CEN] = @dep_one_cmp_cl{
            dose: @bolus{amt:c[AMT]}, KA: m[KA],
            CL: m[CL], V: m[V]}

Here the ‘@dep_one_cmp_cl’ compartment function expects the parameters `m[KA] m[CL] m[V]` to be defined in MODEL_PARAMS, similar to how Nonmem ‘ADVAN2’ expects KA, CL and V to be defined in the $PK section. Also PoPy expects `c[AMT]` to be defined in the data file and Nonmem expects the ‘AMT’ field to exist in the Nonmem data file.

Note PoPy is explicit about the input parameter names that are required for compartment model functions and you can easily change the name of the input `c[X]` and `m[X]` parameters in your script file. In Nonmem you just have to know what the fixed magic words and conventions are. For example, you need to declare the variables ‘KA’, ‘CL’ and ‘V’ carefully in the Nonmem $PK section and have an ‘AMT’ field in your data file.

The PoPy equivalents of the various ‘ADVAN’ analytic compartment models are discussed in the Nonmem Advan1,2,3,4,11,12 to PoPy analytic compartment models section below.

## $MODEL

Nonmem:-

    $MODEL NCOMPARTMENTS=1

This does not have any equivalent in PoPy, you do not need to specify the number of compartments in the DERIVATIVES section. PoPy can count!

## $THETA

Nonmem:-

    $THETA
        (0.001, 0.05, 1)  ; KE

to PoPy:-

    EFFECTS:
        POP: |
            f[KE] ~ unif(0.001, 1) 0.05

In PoPy the fixed effect `f[KE]` is explicitly declared at the POP level, i.e. there is one value for this parameter over the population.

The limits on `f[KE]` are defined using a ~unif() distribution, with the initial value added as suffix.

Note that comments have been added to the Nonmem notation here to make it more readable. PoPy insists on names for all variables.

## $OMEGA

Nonmem:-

    $OMEGA
        0.1  ; KE_isv

to PoPy:-

    EFFECTS:
        POP: |
            f[KE_isv] ~ unif(0.001, +inf) 0.1

In PoPy the fixed effect variance parameter `f[KE_isv]` is explicitly declared at the POP level, i.e. there is one value for this parameter over the population.

The limits on `f[KE_isv]` are defined using a ~unif() distribution, with the initial value added as a suffix. In PoPy you can use the equivalent shorter notation:-

    f[KE_isv] ~ P 0.1

Note that PoPy can deduce automatically that `f[KE_isv]` is a variance by examining the `r[KE]` ~norm() distribution definition from the ID level:-

    EFFECTS:
        ID: |
            r[KE] ~ norm(0, f[KE_isv])

## $SIGMA

Nonmem:-

    $SIGMA
        0.001 FIX ; ANOISE
        0.2 ; PNOISE

to PoPy:-

    EFFECTS:
        POP: |
            f[ANOISE] = 0.001
            f[PNOISE] ~ P0.2

In PoPy the fixed effect noise variance parameters `f[ANOISE]` and `f[PNOISE]` are explicitly declared at the ID level, i.e. there is one value for both parameters over the population.

The ‘=’ sign is used instead of the Nonmem ‘FIX’ keyword. The ‘~P’ notation is a shortcut for defining a ~unif() distribution, which is equivalent to the following:-

    f[PNOISE] ~ unif(0.001, +inf) 0.2

The initial value 0.2 is added as a suffix after the distribution.

Note that PoPy can deduce automatically that `f[ANOISE]` and `f[PNOISE]` are noise parameters (i.e. sigma like variables) by examining the `c[DV_CENTRAL]` ~norm() distribution likelihood definition from the PREDICTIONS section:-

    PREDICTIONS: |
        p[DV_CENTRAL] = s[CENTRAL]
        var = m[ANOISE] + m[PNOISE]*p[DV_CENTRAL]**2
        c[DV_CENTRAL] ~ norm(p[DV_CENTRAL], var)

Where `m[ANOISE]` and `m[PNOISE]` are copies of the `f[ANOISE]` and `f[PNOISE]` parameters as defined in the MODEL_PARAMS section.

## $PK

Nonmem:-

    $PK
        KE = THETA(1) * exp(ETA(1))

to PoPy:-

    MODEL_PARAMS: |
        m[KE] = f[KE] * exp(r[KE])
        m[ANOISE] = f[ANOISE]
        m[PNOISE] = f[PNOISE]

The PoPy MODEL_PARAMS section has exactly the same purpose as the Nonmem $PK section. Some differences are:-

- Nonmem uses numbered THETA and ETA variables, whilst PoPy uses named `f[X]` and `r[X]` variables.
- Nonmem uses bare variable names on the left hand side of equations in the $PK section and exports all of the variables for use in the $DES and $ERROR sections (just ‘KE’ in this case). PoPy requires explicit use of the `m[X]` syntax to export variables from the MODEL_PARAMS section.

You can use the local variable syntax in the PoPy MODEL_PARAMS section e.g.:-

    MODEL_PARAMS: |
        f = f[KE]
        r = r[KE]
        m = f * exp(r)
        m[KE] = m

However only `m[KE]` will be available in the PoPy DERIVATIVES and PREDICTIONS sections not ‘f’, ‘r’ or ‘m’. Some other differences are:-

- PoPy requires that the `f[ANOISE]` and `f[PNOISE]` variables (which are sigmas in Nonmem) are converted to `m[X]` variables, because `f[X]` variables can not be used as inputs to the DERIVATIVES and PREDICTIONS sections.
- Nonmem implements IOV using ‘if’ statements. PoPy MODEL_PARAMS can handle IOV `r[X]` variables without using ‘if’ statements, see DDMoRe0238 Conversion Example.
- Nonmem converts the $PK section into a Fortran function, whilst PoPy converts the MODEL_PARAMS section into executable C++ code.

Otherwise the $PK and MODEL_PARAMS sections are quite similar. They both accept procedural pseudocode, e.g. ‘if’ statements etc. and compute model variables for each row of the data set when fitting PK/PD models.

## $DES

Nonmem:-

    $DES
        DADT(1) = -KE*A(1)

to PoPy:-

    DERIVATIVES: |
        d[CENTRAL] = @bolus{amt:c[AMT]} - m[KE]*s[CENTRAL]

The PoPy DERIVATIVES section has exactly the same of purpose as the Nonmem $DES section. Some differences are:-

- Nonmem uses numbered DADT and A variables, whilst PoPy uses named `d[X]` and `s[X]` variables.
- Nonmem will allow you to use any previously specified variables as input, whereas PoPy restricts input variables to be of type `c[X]` and `m[X]`.
- Nonmem specifies the dosing time, amount and compartment (either bolus or infusion) entirely from the data file, however PoPy has Dosing Functions that have explicitly declared input parameters. The dosing compartment is clearly defined by the dose function location in the DERIVATIVES section. The time of each dose is still defined in the data file. See Dosing Fields for more info.
- Nonmem uses the magic variable ‘T’ to specify continuous time in the $DES section, PoPy uses the more obviously magic `x[TIME]` variable to do the same thing.
- Nonmem converts the $DES section into a Fortran function, whilst PoPy converts the DERIVATIVES section into a C++ function, that can be called by a numerical ordinary differential equation solver.

Otherwise the $DES and DERIVATIVES sections are quite similar. They both accept procedural pseudocode, e.g. ‘if’ statements etc. and generate code that be executed by a numerical ordinary differential equation solver to compute compartment model amount/state variables at time points specified in the data file.

## $ERROR

Nonmem:-

    $ERROR
        Y = F + EPS(1) + F*EPS(2)

to PoPy:-

    PREDICTIONS: |
        p[DV_CENTRAL] = s[CENTRAL]
        var = m[ANOISE] + m[PNOISE] * p[DV_CENTRAL]**2
        c[DV_CENTRAL] ~ norm(p[DV_CENTRAL], var)

The PoPy PREDICTIONS section has the same of purpose as the Nonmem $ERROR section. They both compare observations from the data file with model predictions. In a fitting script these sections compute a likelihood, when simulating these sections produce a noisy measurement prediction.

Some differences are:-

- The Nonmem $error section is much shorter, it relies on the convention that ‘Y’ is the dependant variable ‘DV’ column from the data file and ‘F’ is the model prediction for the compartment specified by the ‘CMT’ column in the data file.
- The PoPy PREDICTIONS explicitly creates `p[X]` prediction variables, often based on `s[X]` amounts from the compartment model, which all have human readable names.
- The Nonmem likelihood is expressed as a sum of ‘EPS’ normal distributions, which Nonmem combines into a single ~norm() distribution to compute the likelihood for each data row.
- The PoPy likelihood is constructed using the ‘~’ notation and uses named Probability Distributions.

In PoPy you have to work out how to compute the ~norm() distribution parameters yourself. Here the mean is simply the model prediction `p[DV_CENTRAL]`. The `m[ANOISE]` and `m[PNOISE]` parameters are separate additive and proportional variances, so have a combined variance as follows:-

    var = m[ANOISE] + m[PNOISE] * p[DV_CENTRAL]**2

Otherwise the $ERROR and PREDICTIONS sections are quite similar. They both accept procedural pseudocode, e.g. ‘if’ statements etc. and generate code that can be used to compute a likelihood value for each row of a data set (when fitting).

## $ESTIMATION

Nonmem:-

    $ESTIMATION METHOD=ITS INTERACTION PRINT=1 MAXEVALS=100

Or

    $ESTIMATION METHOD=FOCE INTERACTION PRINT=1 MAXEVALS=100

to PoPy:-

    FIT_METHODS: [JOE: {max_n_main_iterations: 100}]

Note estimation methods can be called sequentially in Nonmem:-

    $ESTIMATION METHOD=ITS INTERACTION PRINT=1 MAXEVALS=100
    $ESTIMATION METHOD=FOCE INTERACTION PRINT=1 MAXEVALS=100

Similarly the JOE fitting method can be called sequentially in PoPy as:-

    FIT_METHODS:
        - JOE: {max_n_main_iterations: 100}
        - FOCE: {max_n_main_iterations: 100}

In PoPy the JOE fitting method, optimises the same ObjV as Nonmem FOCE and ITS, but the fitting algorithm is slightly different.

Note that in PoPy v1.0.5 the most accurate, but slower fitting method is ND specifed as follows:-

    FIT_METHODS:
        - ND: {max_n_main_iterations: 100}

ND optimises the FOCE ObjV so can be compared to Nonmem FOCE directly.