Editing ferx model files programmatically

ferx model files (.ferx) are plain text, so you can create and modify them entirely from R without opening an editor. This article covers the three functions that support that workflow: ferx_model_new(), ferx_model_section(), and ferx_model_set_section().

Tip

See also Chapter 3 (Model DSL) in the ferx book for a complete reference with examples.


Starting from scratch

ferx_model_new() writes a skeleton .ferx file for one of five built-in templates: "1cpt_oral" (default), "1cpt_iv", "2cpt_oral", "2cpt_iv", and "ode".

Pass print = TRUE to preview a template in the console without writing any file — useful when you are exploring or copy-pasting a starting point:

ferx_model_new(print = TRUE)
ferx_model_new(template = "2cpt_iv", print = TRUE)

To write a file, supply a path. By default the file is also opened in your editor (edit = TRUE); pass edit = FALSE to skip that:

ferx_model_new("my_model.ferx", edit = FALSE)

Inspecting a section

ferx_model_section() extracts the body of a named section and prints it. Start with the bundled warfarin example:

ex <- ferx_example("warfarin")
ferx_model_section(ex$model, "parameters")

The return value is the character vector of lines, returned invisibly, so you can capture it:

params <- ferx_model_section(ex$model, "parameters")
params

Scripted read-modify-write

The extract → modify → write-back pattern lets you change a model programmatically without an editor. Here we raise the initial CL estimate in the parameters section and write the result to a temporary copy:

# work in a fresh temp file so re-knitting never reuses stale state
model_path <- tempfile(fileext = ".ferx")
stopifnot(file.copy(ex$model, model_path))

# read the section
lines <- ferx_model_section(model_path, "parameters")

# modify: change the TVCL initial value from 0.134 to 0.5
lines <- sub("TVCL\\([^,]+,", "TVCL(0.5,", lines)

# write back
ferx_model_set_section(model_path, "parameters", lines)

# verify
ferx_model_section(model_path, "parameters")

ferx_model_set_section() replaces only the targeted section; all other sections are left untouched.


Console-only workflow: create, tweak, fit

You can go from nothing to an estimated model without ever opening a text editor. The steps below create a one-compartment oral model file, switch the estimation method to FOCEI, and run ferx_fit():

model_path <- file.path(tempdir(), "run1.ferx")

# 1. Write a skeleton
ferx_model_new(model_path, template = "1cpt_oral", edit = FALSE)

# 2. Switch method to focei
ferx_model_set_section(model_path, "fit_options", c(
  "  method     = focei",
  "  maxiter    = 300",
  "  covariance = true"
))

# 3. Fit (uses the bundled warfarin data for illustration)
ex   <- ferx_example("warfarin")
fit  <- ferx_fit(model_path, ex$data)
print(fit)

Opening a model in an editor

ferx_model_edit() copies a bundled example to a destination directory (or leaves a user-owned file in place) and opens it in your editor. It is the interactive companion to the scripted functions above.

# Copy the bundled warfarin model to tempdir() and open it
my_model <- ferx_model_edit(ex$model, dest = tempdir())

# After editing, save the result under a new name for version control
ferx_model_edit("run1.ferx", save_as = "run2.ferx")

The dest argument controls where the copy lands. If the source file is already outside the installed package directory, ferx_model_edit() edits it in place (no copy) unless dest is set explicitly.


Validating a model file

ferx_model_validate() checks that all required sections are present and reports the result. Use it after creating or editing a model to catch missing sections before running the optimizer:

ferx_model_validate(ex$model)

A model missing required sections produces a clear error list:

bad <- tempfile(fileext = ".ferx")
writeLines(c(
  "[parameters]",
  "  theta TVCL(1.0, 0.001, 100.0)",
  "[structural_model]",
  "  pk one_cpt_oral(cl=CL, v=V, ka=KA)"
), bad)
ferx_model_validate(bad)

Overwrite guards

By default ferx_model_new() refuses to overwrite an existing file:

model_path <- tempfile(fileext = ".ferx")
ferx_model_new(model_path, edit = FALSE)            # creates the file

tryCatch(
  ferx_model_new(model_path, edit = FALSE),          # would overwrite — error
  error = function(e) message(conditionMessage(e))
)

Opt in explicitly with overwrite = TRUE:

ferx_model_new(model_path, template = "1cpt_iv", overwrite = TRUE, edit = FALSE)
ferx_model_section(model_path, "structural_model")

Function reference

Function Purpose
ferx_model_new() Write a skeleton .ferx file from a template
ferx_model_section() Extract the body of a named section
ferx_model_set_section() Replace the body of a named section
ferx_get_section() Print a section mid-pipe (returns object unchanged)
ferx_set_section() Rewrite a section mid-pipe (returns object unchanged)
ferx_model_edit() Copy + open a model in your editor
ferx_model_validate() Check that all required sections are present