model-editing.Rmdferx model files (.ferx) are plain text, so you can
create and modify them entirely from R without opening an editor. This
vignette covers the three functions that support that workflow:
ferx_model_new(), ferx_model_section(), and
ferx_model_set_section().
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)
#> # One-compartment oral PK model
#>
#> [parameters]
#> theta TVCL(1.0, 0.001, 100.0)
#> theta TVV(10.0, 0.1, 1000.0)
#> theta TVKA(1.0, 0.01, 50.0)
#>
#> omega ETA_CL ~ 0.09
#> omega ETA_V ~ 0.09
#> omega ETA_KA ~ 0.25
#>
#> sigma PROP_ERR ~ 0.01
#>
#> [individual_parameters]
#> CL = TVCL * exp(ETA_CL)
#> V = TVV * exp(ETA_V)
#> KA = TVKA * exp(ETA_KA)
#>
#> [structural_model]
#> pk one_cpt_oral(cl=CL, v=V, ka=KA)
#>
#> [error_model]
#> DV ~ proportional(PROP_ERR)
#>
#> [fit_options]
#> method = foce
#> maxiter = 300
#> covariance = 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)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")
#> # [parameters]
#> theta TVCL(0.134, 0.001, 10.0)
#> theta TVV(8.1, 0.1, 500.0)
#> theta TVKA(1.0, 0.01, 50.0)
#>
#> omega ETA_CL ~ 0.07
#> omega ETA_V ~ 0.02
#> omega ETA_KA ~ 0.40
#>
#> sigma PROP_ERR ~ 0.01The return value is the character vector of lines, returned invisibly, so you can capture it:
params <- ferx_model_section(ex$model, "parameters")
#> # [parameters]
#> theta TVCL(0.134, 0.001, 10.0)
#> theta TVV(8.1, 0.1, 500.0)
#> theta TVKA(1.0, 0.01, 50.0)
#>
#> omega ETA_CL ~ 0.07
#> omega ETA_V ~ 0.02
#> omega ETA_KA ~ 0.40
#>
#> sigma PROP_ERR ~ 0.01
params
#> [1] " theta TVCL(0.134, 0.001, 10.0)" " theta TVV(8.1, 0.1, 500.0)"
#> [3] " theta TVKA(1.0, 0.01, 50.0)" ""
#> [5] " omega ETA_CL ~ 0.07" " omega ETA_V ~ 0.02"
#> [7] " omega ETA_KA ~ 0.40" ""
#> [9] " sigma PROP_ERR ~ 0.01" ""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 the vignette 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")
#> # [parameters]
#> theta TVCL(0.134, 0.001, 10.0)
#> theta TVV(8.1, 0.1, 500.0)
#> theta TVKA(1.0, 0.01, 50.0)
#>
#> omega ETA_CL ~ 0.07
#> omega ETA_V ~ 0.02
#> omega ETA_KA ~ 0.40
#>
#> sigma PROP_ERR ~ 0.01
# 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")
#> # [parameters]
#> theta TVCL(0.5, 0.001, 10.0)
#> theta TVV(8.1, 0.1, 500.0)
#> theta TVKA(1.0, 0.01, 50.0)
#>
#> omega ETA_CL ~ 0.07
#> omega ETA_V ~ 0.02
#> omega ETA_KA ~ 0.40
#>
#> sigma PROP_ERR ~ 0.01ferx_model_set_section() replaces only the targeted
section; all other sections are left untouched.
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)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.
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)
#> Validating: warfarin.ferx
#>
#> Sections present:
#> parameters [ok]
#> individual_parameters [ok]
#> structural_model [ok]
#> error_model [ok]
#> fit_options [ok] (optional)
#>
#> Result: VALIDA 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)
#> Validating: file2891795ab419.ferx
#>
#> Sections present:
#> parameters [ok]
#> individual_parameters [MISSING]
#> structural_model [ok]
#> error_model [MISSING]
#>
#> Result: INVALID
#> * Missing required section: [individual_parameters]
#> * Missing required section: [error_model]
#> * Parse error: Missing [error_model] blockBy 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
#> Created /tmp/RtmpR9M3HE/file289173bcd227.ferx
tryCatch(
ferx_model_new(model_path, edit = FALSE), # would overwrite — error
error = function(e) message(conditionMessage(e))
)
#> /tmp/RtmpR9M3HE/file289173bcd227.ferx already exists. Use overwrite = TRUE to replace it.Opt in explicitly with overwrite = TRUE:
ferx_model_new(model_path, template = "1cpt_iv", overwrite = TRUE, edit = FALSE)
#> Created /tmp/RtmpR9M3HE/file289173bcd227.ferx
ferx_model_section(model_path, "structural_model")
#> # [structural_model]
#> pk one_cpt_iv_bolus(cl=CL, v=V)