PharmFlux quickstart and user guide for R
Build a unit-aware model, simulate doses, compare parameters, and fit synthetic observations using PharmFlux's R package. The native engine runs locally; these examples require no Ubi account, Julia runtime, or simulation service. All parameter values and data in this guide are synthetic.
This guide targets the source-based 0.1.0 R API. The R binding is a development package; these instructions do not assume a CRAN or R-universe release exists. Run the R blocks in order in one session. The complete example script runs the same workflow and saves its outputs. Keep this guide with the source version you use.
Contents
- Install and verify
- Quickstart
- Read and plot results
- Change parameters and dosing
- Choose sampling and solver settings
- Scan parameters and run Morris screening
- Fit an individual model
- Simulate and fit NONMEM style rows
- Fit population data
- Save a reproducible analysis
- Handle errors and troubleshoot
- API reference and further reading
Install and verify
You need R, its compatible C compiler toolchain, Rust 1.98 or later, Cargo, and
a complete PharmFlux source checkout. The local installation uses the Rust
workspace outside bindings/r; copying that directory by itself is insufficient.
Build from the repository root.
Install the R serialization dependency in your usual R session:
install.packages("jsonlite")
In a macOS or Linux terminal, select a user library and install the package:
mkdir -p "$HOME/R/pharmflux-library"
export R_LIBS_USER="$HOME/R/pharmflux-library"
export R_HOME="$(R RHOME)"
cargo fetch --locked
cargo build -p pharmflux-r --release --locked
R CMD INSTALL --library="$R_LIBS_USER" bindings/r
Rscript -e 'library(pharmflux); print(packageVersion("pharmflux"))'
The expected version for this source is 0.1.0. The R build uses
--offline --locked, so the initial cargo fetch --locked matters. An
offline build still needs its locked registry dependencies cached in
CARGO_HOME. The native library links to your installed R runtime; use the
compiler toolchain appropriate to that R distribution.
In a new RStudio session, include the installation library before loading:
.libPaths(c(path.expand("~/R/pharmflux-library"), .libPaths()))
library(pharmflux)
library(jsonlite)
packageVersion("pharmflux")
The local build has been checked on macOS ARM64. Do not interpret this as a Linux or Windows installation guarantee. On Windows, compatible R build tools and the Rust toolchain are required; cross-platform source installation needs its own verification. Restart R after replacing a loaded native package.
If you have the development packaging script, a standalone source archive can be prepared with its own bundled Rust workspace:
# The source destination must not already exist.
python3 scripts/prepare-r-source.py /tmp/pharmflux-r-source
mkdir -p /tmp/pharmflux-r-library
R CMD INSTALL --library=/tmp/pharmflux-r-library \
/tmp/pharmflux-r-source/pharmflux_0.1.0.tar.gz
This archive still requires cached Cargo registry dependencies; it does not bundle every registry dependency. See R binding installation notes for packaging details and the exact qualification scope.
Quickstart
The model stores central drug amount in mg and reports concentration cp in
mg/L. Clearance is 0.3 L/h and volume is 3 L, so the elimination rate is
0.1 per hour. After a 6 mg bolus, cp(t) = 2 exp(-0.1 t).
Compile the model
library(pharmflux)
library(jsonlite)
source <- "model quickstart_one_compartment
time_unit = h
parameters
cl = 0.3 [L/h] log
v = 3 [L] log
states
central = 0 [mg]
dynamics
d/dt(central) = input_central - cl / v * central
outputs
cp = central / v [mg/L]
dosing
central [mg] = 1
end
"
model <- compile_model(source)
q <- function(value, unit) list(value = value, unit = unit)
The dosing declaration enables delivery to central; the input_central
term makes an infusion contribute to the dynamics. A bolus applies an amount
jump. Model text, JSON model text, and appropriately structured R lists are
accepted. A file path is not source text: read it with
paste(readLines("model.pfx", warn = FALSE), collapse = "\n").
Define the experiment and run it
Explicit observations choose exact times and sides. All six observations here are post-event readouts, including the concentration immediately after the time-zero bolus.
times <- c(0, 1, 2, 4, 8, 12)
request <- list(
schema = "pharmflux.run/v0.1",
solver = "diffsol_bdf",
budgets = list(solver_callbacks = 1000000L,
events = 100L, output_values = 10000L),
regimen = list(
end = q(12, "h"), samples = list(),
observations = lapply(times, function(t) list(time = q(t, "h"), side = "post")),
administrations = list(list(
target = "central", time = q(0, "h"), amount = q(6, "mg"),
delivery = list(kind = "bolus"), bioavailability = 1
)),
rtol = 1e-10, atol = 1e-12
)
)
result <- simulate_model(model, request)
frame <- result_data_frame(result)
cp <- frame[frame$OUTPUT == "cp", ]
stopifnot(
identical(cp$TIME, as.double(times)),
all(cp$SIDE == "post"),
max(abs(cp$VALUE - 2 * exp(-0.1 * cp$TIME))) < 1e-8
)
print(cp)
Expected concentrations are 2.000000 mg/L at 0 h, 1.340640 mg/L at 4 h, and 0.602388 mg/L at 12 h. The analytic comparison checks scientific behavior as well as successful execution.
Compile once and reuse model. Each simulation starts from its own initial
conditions and request. It does not continue the previous trajectory.
model holds a native pointer belonging to this R session; recreate it from
source in another session or process.
Keep JSON arrays and objects distinct
The binding encodes lists with jsonlite::toJSON(auto_unbox = TRUE).
Use unnamed lists for arrays of records and named lists for objects.
For example, administrations = list(list(...)) must remain an array even
when it contains one dose. For a one-parameter sensitivity selection, use
list("cl"); a length-one character vector may be encoded as a scalar.
Keep tolerances as doubles and row/budget counts as whole numbers.
Read versioned JSON without simplifying nested records:
# Optional file workflow after saving a request later in this guide:
# request <- fromJSON("run.request.json", simplifyVector = FALSE)
Unknown fields and malformed arrays are rejected. NA is not an ordinary
scientific quantity; handle missing measurements explicitly through MDV in
the dataset adapter.
Read and plot results
result_data_frame() returns long data with TIME, SIDE, OUTPUT, VALUE,
and UNIT. It attaches time_unit and execution_identity attributes and
preserves missing numeric values as NA. Output names cannot overwrite the
table's metadata columns because each name appears in OUTPUT.
plot(cp$TIME, cp$VALUE, type = "b",
xlab = paste0("Time (", attr(frame, "time_unit"), ")"),
ylab = "cp (mg/L)")
write.csv(frame, "trajectory.csv", row.names = FALSE)
Filter the desired output before plotting. Retain side labels when comparing values at an event time. States are returned only when exposed as model outputs. CSV does not preserve execution attributes, so retain the complete JSON result as well.
For a tidyverse workflow, the existing data frame can be passed to dplyr or ggplot2. Those packages are optional; they are not required for the native engine or the examples in this guide.
Change parameters and dosing
R lists use copy-on-modify semantics, so assigning changed <- request and
editing a nested field creates a changed experiment without altering the
original request. The native model pointer still refers to the same compiled
model.
changed <- request
changed$parameters <- list(cl = q(0.6, "L/h"), v = q(3, "L"))
faster_clearance <- simulate_model(model, changed)
repeated <- request
repeated$regimen$administrations[[1]][["repeat"]] <- list(
interval = q(4, "h"), additional = 2L
)
repeated_result <- simulate_model(model, repeated) # Doses at 0, 4, and 8 h.
infusion <- request
infusion$regimen$administrations[[1]]$delivery <- list(
kind = "infusion",
span = list(kind = "duration", duration = q(2, "h"))
)
infusion_result <- simulate_model(model, infusion) # 6 mg over 2 h.
additional counts administrations after the first. Rate-based infusion uses
span = list(kind = "rate", rate = q(3, "mg/h")) instead of duration.
Supply exactly one span type. Bioavailability is a fraction from 0 to 1;
the delivered amount also passes through the model's amount-to-state scale.
Request lag and model-declared lag add together.
Compatible units convert explicitly: an administration in ug can target a dosing map in mg. Amount and concentration have different dimensions; use a declared volume or dosing scale to connect them. A positive infusion rate needs amount/time units. Consult the unit specification before using a new unit spelling.
Declare covariates in the model and supply quantity values through
regimen$covariates; step covariates can also use covariate_changes.
Derived individual expressions are recomputed from their inputs and cannot
be directly overridden. Constant and step numeric covariates are supported;
categorical covariates and linear interpolation are outside the current scope.
Build an oral two compartment model
To model first-order absorption with central/peripheral distribution, add a depot, intercompartmental clearance, and peripheral volume. Expose amounts as outputs so the amount balance can be inspected alongside concentration.
oral_source <- "model quickstart_oral_two_compartment
time_unit = h
parameters
cl = 1 [L/h] log
vc = 5 [L] log
vp = 10 [L] log
exchange = 0.5 [L/h] log
ka = 1 [1/h] log
states
depot = 0 [mg]
central = 0 [mg]
peripheral = 0 [mg]
dynamics
d/dt(depot) = -ka * depot
d/dt(central) = ka * depot - cl / vc * central - exchange * (central / vc - peripheral / vp)
d/dt(peripheral) = exchange * (central / vc - peripheral / vp)
outputs
cp = central / vc [mg/L]
depot_amount = depot [mg]
central_amount = central [mg]
peripheral_amount = peripheral [mg]
dosing
depot [mg] bolus = 1
end
"
oral_model <- compile_model(oral_source)
oral_request <- request
oral_request$regimen$administrations[[1]]$target <- "depot"
oral_request$regimen$administrations[[1]]$amount <- q(100, "mg")
oral_request$regimen$administrations[[1]]$bioavailability <- 0.8
oral_result <- simulate_model(oral_model, oral_request)
oral_frame <- result_data_frame(oral_result)
stopifnot(
oral_frame$VALUE[oral_frame$TIME == 0 & oral_frame$OUTPUT == "cp"] == 0,
oral_frame$VALUE[oral_frame$TIME == 0 & oral_frame$OUTPUT == "depot_amount"] == 80,
all(oral_frame$VALUE >= -1e-8)
)
The available 80 mg enters the depot and reaches central through first-order absorption. The depot permits bolus delivery only; there is no infusion forcing in its equation. Adding the three amount derivatives cancels internal transfers and leaves central elimination. These are simple synthetic mechanism assumptions, not drug-specific parameter estimates. For larger PBPK/QSP systems, inspect conservation and domains and use state-specific tolerances.
Choose sampling and solver settings
At a bolus time, pre selects the amount before the jump and post selects
it after the jump. Dataset row order never substitutes for this choice.
at_dose <- request
at_dose$regimen$observations <- list(
list(time = q(0, "h"), side = "pre"),
list(time = q(0, "h"), side = "post")
)
event_result <- simulate_model(model, at_dose)
stopifnot(identical(unlist(event_result$outputs[[1]]$values), c(0, 2)))
An explicit observation plan preserves order and duplicates. For trajectory
sampling, omit observations and populate samples; event boundaries may
add rows. Always inspect returned times and sides. Do not combine nonempty
samples with an observation plan, or supply an empty observation plan.
This guide uses BDF. Simulation also supports diffsol_tsit45,
diffsol_esdirk34, diffsol_tr_bdf2, diffsol_rosenbrock23, and
diffsol_rodas5p within their supported ODE scope. No unknown method selects
a fallback. Sensitivity-based fitting currently requires BDF. An
analytic_linear_pk run needs a model that declares linear_pk; an arbitrary
linear-looking differential equation does not select that engine automatically.
rtol is dimensionless, while scalar atol uses each state's numeric unit.
For differing state scales, remove atol and supply a complete quantity map:
absolute_tolerances = list(central = q(1e-12, "mg")) for our single state.
Use exactly one absolute-tolerance mode. Check numerical stability by comparing
tighter tolerances and an independent or analytic reference where possible.
Budgets constrain solver callbacks, events, and output values. They are work limits rather than deadlines. More work cannot correct invalid dimensions, equation domains, or an uninformative fitting design.
Scan parameters and run Morris screening
Scans evaluate up to 25 explicit parameter points, each with a unique ID. The base run has per-run budgets, and the scan also needs aggregate budgets.
scan_request <- list(
schema = "pharmflux.scan/v0.1", run = request,
points = lapply(c(0.3, 0.6, 1.2), function(value) list(
id = paste0("cl_", value), parameters = list(cl = q(value, "L/h"))
)),
total_budgets = list(solver_callbacks = 3000000L,
events = 300L, output_values = 30000L)
)
scan <- scan_model(model, scan_request)
scan_table <- do.call(rbind, lapply(scan$results, function(point) {
data <- result_data_frame(point$result)
data$POINT <- point$id
data
}))
print(scan_table[scan_table$TIME == 12, ])
A scan compares deterministic scenarios; it does not provide population variability or confidence intervals. Use biologically defensible ranges.
Morris screening calculates elementary effects over chosen parameter ranges at one output row. Two parameters and two trajectories require six runs here.
morris_request <- list(
schema = "pharmflux.morris/v0.1",
problem = list(kind = "generated", request = list(
run = request,
ranges = list(
list(parameter = "cl", lower = q(0.1, "L/h"), upper = q(0.7, "L/h")),
list(parameter = "v", lower = q(3, "L"), upper = q(6, "L"))
),
trajectory_count = 2L, num_levels = 4L, seed = 42L,
total_budgets = list(solver_callbacks = 6000000L,
events = 600L, output_values = 60000L),
output = "cp", row = 3L # Zero-based: the selected 4 h observation.
))
)
morris <- morris_model(model, morris_request)
print(morris$result$effects)
mu_star is average absolute effect magnitude; sigma describes variation
across elementary effects. Their interpretation depends on the output,
sampling row, ranges, design grid and seed. The 25-run scan bound also limits
Morris designs. The R API does not expose a standalone forward-sensitivity
runner; fit_model() compiles the sensitivity selection needed for a fit.
Fit an individual model
This example estimates clearance and volume from exact synthetic observations
generated with cl = 0.6 L/h and v = 4.5 L. It uses a fixed additive
normal SD of 0.1 mg/L. A real analysis needs an error model justified for its
measurement process.
sensitivity_request <- list(
schema = "pharmflux.sensitivity/v0.1", run = request,
with_respect_to = list("cl", "v"),
derivative_absolute_tolerances = list(
cl = list(central = q(1e-12, "mg.h/L")),
v = list(central = q(1e-12, "mg/L"))
)
)
observations <- lapply(seq_along(times), function(index) list(
row = index - 1L, output = "cp",
value = q(6 / 4.5 * exp(-0.6 * times[index] / 4.5), "mg/L"),
error = list(additive_sd = q(0.1, "mg/L"), proportional_sd = 0)
))
fit_request <- list(
schema = "pharmflux.fit/v0.1",
problem = list(kind = "individual", request = list(
objective = list(simulation = sensitivity_request,
observations = observations, priors = list()),
bounds = list(
list(parameter = "cl", lower = q(0.05, "L/h"), upper = q(1.2, "L/h")),
list(parameter = "v", lower = q(1, "L"), upper = q(8, "L"))
),
max_evaluations = 400L, max_iterations = 100L, gradient_tolerance = 1e-5
))
)
fitted <- fit_model(model, fit_request)
print(fitted$fit$status)
print(fitted$fit$parameters)
stopifnot(
fitted$fit$status == "converged",
abs(fitted$fit$parameters$cl$value - 0.6) < 1e-4,
abs(fitted$fit$parameters$v$value - 4.5) < 1e-4
)
replay <- request
replay$parameters <- fitted$fit$parameters
fit_predictions <- simulate_model(model, replay)
Native row values start at zero even in R. Our explicit observation plan
makes row = 0 the post-dose measurement at 0 h. Derivative absolute
tolerances have state-unit divided by parameter-unit, rather than the state's
unit alone. The selected parameter order must match the fitting contract.
The engine can return a result with evaluation_limit; inspect the status
before using estimates. Review objective, work count, boundaries and prediction
replay. A fit to exact noiseless synthetic data verifies implementation; it
does not establish confidence-interval calibration or clinical validity.
priors = list() requests likelihood fitting. Supported priors produce MAP
fits; consult Gaussian objectives for their native
representation. The R binding does not currently expose Python's derivative-free
scalar fitting method.
Simulate and fit NONMEM style rows
The R helpers convert a restricted record format into explicit requests. They are useful for tabular PK workflows, but do not import a NONMEM control stream or infer endpoint, covariate, censoring or missing-value semantics.
To use an existing CSV instead, read and validate it before calling the adapters.
Preserve string IDs such as 001, rather than converting them to numbers:
# Replace the synthetic data below with your own validated file when needed.
# data <- read.csv("observations.csv", stringsAsFactors = FALSE,
# colClasses = c(ID = "character"))
# Check numeric TIME/AMT/DV, explicit MDV, CMT mappings and declared units.
Construct dose and observation rows with separate CMT values. This example has two subjects, both following the same synthetic concentration profile.
data <- do.call(rbind, lapply(c("A", "B"), function(id) {
data.frame(
ID = id, TIME = c(0, 0, 1, 2, 4), EVID = c(1L, 0L, 0L, 0L, 0L),
CMT = c(1L, 2L, 2L, 2L, 2L), AMT = c(6, 0, 0, 0, 0),
MDV = c(1L, 0L, 0L, 0L, 0L),
DV = c(NA_real_, 6 / 4.5 * exp(-0.6 * c(0, 1, 2, 4) / 4.5))
)
}))
rownames(data) <- NULL
run_template <- request
run_template$regimen$samples <- list()
run_template$regimen$observations <- NULL
run_template$regimen$administrations <- list()
dataset <- nonmem_requests(
data, run_template, compartments = c("1" = "central"),
time_unit = "h", amount_unit = "mg", observation_side = "post"
)
predicted_data <- simulate_data(model, dataset)
print(head(predicted_data$data))
The dataset supplies doses and selected observations. The template supplies parameters, static covariates, horizon, solver, and budgets. Its schedules, including resets, covariate changes and checkpoints, must be empty. Budgets apply per subject and subjects execute sequentially with one compiled model.
Required columns are ID, TIME, and EVID. Optional supported columns are DV,
MDV, AMT, CMT, RATE, ADDL, II, and SS. EVID = 0, MDV = 0 selects a
measurement; MDV 1 excludes it. EVID = 1 is a dose with positive AMT and a
mapped CMT. RATE 0 is bolus; positive RATE requires explicit rate_unit and
describes a finite infusion. ADDL/II expand repeats. Nonzero SS, EVID 2/3/4,
negative RATE, unknown columns, and dose fields on observation rows fail.
The converter does not extend the template's time horizon.
Prepare individual or pooled fits
Clear the individual fit template's run and observations so that the prepared
dataset can supply them. Map observation CMT 2 to output cp and its error
model. This endpoint mapping is separate from dose CMT 1 to central.
fit_template <- fit_request
fit_template$problem$request$objective$simulation$run <- NULL
fit_template$problem$request$objective$observations <- list()
endpoints <- list("2" = list(
output = "cp", value_unit = "mg/L",
error = list(additive_sd = q(0.1, "mg/L"), proportional_sd = 0)
))
prepared <- fit_data_requests(dataset, fit_template, endpoints, mode = "pooled")
data_fit <- fit_data(model, prepared)
stopifnot(data_fit$results[[1]]$fit$status == "converged")
print(fit_parameter_data_frame(data_fit))
diagnostics <- fit_observation_data_frame(model, data_fit)
print(head(diagnostics))
Use mode = "individual" for a separate fit per subject and inspect every
status. Pooled fitting shares selected parameters across subjects and has no
random effects. A pooled MAP prior is counted once; separate individual MAP
fits each receive the prior.
SOURCE_ROW is one-based in the original R data, while RESULT_ROW is
zero-based in the native result. Duplicate observations remain separate
likelihood terms. Included DV values must be finite; set MDV explicitly to
exclude missing measurements. fit_observation_data_frame() replays converged
fits and incurs an additional simulation per subject. It preserves observation
sides and reports separate DV/PRED units. RESID is NA when those units
differ; the table does not silently convert them for subtraction.
Fit population data
FOCEI and SAEM use one population request with per-subject regimens and a
lognormal random-effect contract. This four-subject example estimates typical
clearance, holding volume and eta variance fixed. Fixing omega = 0.04
means eta SD is 0.2 on the log scale; it is not a clearance-unit variance.
etas <- c(-0.22, -0.08, 0.08, 0.22)
population_rows <- do.call(rbind, lapply(seq_along(etas), function(index) {
t <- c(1, 2, 4)
data.frame(
ID = paste0("S", index), TIME = c(0, t), EVID = c(1L, 0L, 0L, 0L),
CMT = c(1L, 2L, 2L, 2L), AMT = c(6, 0, 0, 0), MDV = c(1L, 0L, 0L, 0L),
DV = c(NA_real_, 6 / 4.5 * exp(-0.6 * exp(etas[index]) * t / 4.5))
)
}))
rownames(population_rows) <- NULL
pop_sensitivity <- sensitivity_request
pop_sensitivity$with_respect_to <- list("cl")
pop_sensitivity$derivative_absolute_tolerances$v <- NULL
pop_sensitivity$run <- run_template
pop_sensitivity$run$parameters <- list(cl = q(0.5, "L/h"), v = q(4.5, "L"))
population_template <- list(
schema = "pharmflux.fit/v0.1",
problem = list(kind = "focei", request = list(
subjects = list(list(simulation = pop_sensitivity,
observations = list(), priors = list())),
fixed_effects = list(list(parameter = "cl", lower = q(0.35, "L/h"),
upper = q(0.85, "L/h"))),
random_effects = list(list(parameter = "cl")),
omega = list(list(0.04)), omega_diagonal_bounds = list(list(0.04, 0.04)),
eta_bound = 1.5, max_evaluations = 30000L,
max_iterations = 80L, gradient_tolerance = 0.005, seed = 17L
))
)
population_endpoints <- list("2" = list(
output = "cp", value_unit = "mg/L",
error = list(additive_sd = q(0.05, "mg/L"), proportional_sd = 0)
))
population_prepared <- population_fit_data_requests(
population_rows, population_template, compartments = c("1" = "central"),
endpoints = population_endpoints, time_unit = "h", amount_unit = "mg",
observation_side = "post"
)
population_fit <- fit_population_data(model, population_prepared)
population <- population_fit$result$population
print(population$status)
print(population$objective_kind)
print(population$fixed_effects)
stopifnot(
population$status == "converged",
abs(population$fixed_effects$cl$value - 0.6) < 0.06
)
subject_table <- population_fit_subject_data_frame(population_fit)
population_diagnostics <- population_fit_observation_data_frame(population_fit)
print(subject_table)
print(head(population_diagnostics))
Inspect population_prepared$request before fitting. Its mapping preserves
one-based SOURCE_ROW and zero-based subject, observation and result indices.
Subject order follows first appearance in the input. FOCEI diagnostics report
DV, population prediction PRED, individual prediction IPRED, and source rows.
Subject tables include eta and the corresponding individual parameter.
Residuals are missing when measurement and prediction units differ.
Allowing Omega bounds to vary changes this example into variance estimation and requires a sufficiently informative cohort. Two random effects, error estimation and uncertainty have method-specific limits; do not infer support for an arbitrary nlmixr2 model or fitting configuration.
Run a short SAEM example
The same adapter can construct SAEM requests. Eight iterations intentionally illustrate a limited result; they are not a recommended estimation budget.
saem_template <- population_template
saem_template$problem$kind <- "saem"
saem_template$problem$request$max_iterations <- 8L
saem_prepared <- population_fit_data_requests(
population_rows, saem_template, compartments = c("1" = "central"),
endpoints = population_endpoints, time_unit = "h", amount_unit = "mg",
observation_side = "post"
)
saem_fit <- fit_population_data(model, saem_prepared)
saem <- saem_fit$result$population
print(saem$status)
print(saem$objective_kind)
saem_subjects <- population_fit_subject_data_frame(saem_fit)
saem_observations <- population_fit_observation_data_frame(saem_fit)
A converged result reports saem_marginal_objective_function_value. A limited
run may return saem_complete_data_surrogate; do not compare it as if it
were a final marginal objective. saem_observations can be NULL when the
native result supplies no fitted-observation rows. Inspect the returned object
and method-specific status before requesting diagnostics or interpreting fits.
The native SAEM contract includes one- and two-effect lognormal subject effects with Gaussian residual error and supported common additive-error estimation. This public version does not support lognormal observation errors, censored observations, a fitted-covariate request field, or SAEM uncertainty. See the SAEM guide for exact combinations and limitations. Record seeds, examine stability across seeds, and verify recovery on a cohort representative of your intended use.
Save a reproducible analysis
Preserve source, requests, complete results, rows, mappings, and software versions. Do not serialize a native pointer as the portable model artifact.
dir.create("pharmflux-analysis", showWarnings = FALSE)
writeLines(source, "pharmflux-analysis/model.pfx")
save_json <- function(object, filename) {
write_json(object, file.path("pharmflux-analysis", filename),
auto_unbox = TRUE, pretty = TRUE, digits = NA, null = "null")
}
save_json(request, "run.request.json")
save_json(result, "run.result.json")
save_json(fit_request, "fit.request.json")
save_json(fitted, "fit.result.json")
save_json(population_prepared$request, "population.request.json")
save_json(population_fit$result, "population.result.json")
write.csv(population_fit$mapping, "pharmflux-analysis/population.mapping.csv", row.names = FALSE)
saveRDS(population_fit, "pharmflux-analysis/population-fit.rds")
write.csv(population_rows, "pharmflux-analysis/population.rows.csv", row.names = FALSE)
saveRDS(frame, "pharmflux-analysis/trajectory.rds")
writeLines(capture.output(sessionInfo()), "pharmflux-analysis/session-info.txt")
print(result$identity)
Save the native population_fit$result envelope as JSON; the classed R wrapper
and its source mapping are stored separately as RDS and CSV. RDS retains
data-frame attributes in an R workflow; JSON retains the native
execution identity for interchange. That identity records model content,
resolved bindings, the run request, compiler source and backend/build details.
It identifies the execution, not the model's scientific suitability.
To reload the simulation, read its request with simplifyVector = FALSE,
compile the saved source, and call simulate_model() again. Do not share the
compiled pointer across processes or rely on it surviving saveRDS().
Compile inside each parallel R worker. The tabular simulation helper itself
executes subjects sequentially.
Handle errors and troubleshoot
Scientific compiler/engine failures use class pharmflux_error with a code,
message, and available scientific/source-location fields. R adapter validation
can instead raise an ordinary R error. Catch the classes appropriate to the
operation you call.
invalid <- request
invalid$parameters <- list(cl = q(1, "h"))
caught <- tryCatch(
simulate_model(model, invalid),
pharmflux_error = function(error) {
print(error$code)
message(conditionMessage(error))
error
}
)
stopifnot(inherits(caught, "pharmflux_error"))
stopifnot(identical(simulate_model(model, request), result))
Failures produce no partial scientific result. A failed request does not replace the compiled model or retain its bindings. Some errors have fallback line/column information rather than exact expression spans.
| Symptom | Action |
|---|---|
| R cannot find the package | Check .libPaths() and the library passed to R CMD INSTALL. |
| Install fails to find Cargo or R headers | Check PATH, R_HOME, the complete workspace, and the compiler toolchain for this R distribution. |
| Offline compilation fails | Fetch the locked Cargo dependencies before installation. |
| Missing helper after reinstall | Restart R and check find.package("pharmflux") for an older library copy. |
| A one-element field becomes a JSON scalar | Use an unnamed list for the native array, particularly with_respect_to. |
| Unit or unknown-symbol error | Compare model declarations, quantity dimensions and permitted input expressions. |
| Wrong dose-time value | Inspect SIDE and request pre/post explicitly. |
| Fit uses the wrong row | Convert from one-based R source rows to zero-based native result indices; inspect the adapter mapping. |
| Data preparation rejects DV | Use explicit MDV for excluded measurements; included values must be finite. |
| Dataset rejects columns or doses | Supply explicit CMT maps and RATE units; remove unsupported columns only after deciding how their information belongs in the model. |
| Fit hits limits or parameter bounds | Check identifiability, starting values, scientific bounds and error model before extending budgets. |
| Residuals are NA | Compare DV/PRED units and missingness; no implicit table-level unit conversion is performed. |
| SAEM observation table is NULL | Inspect status and native fitted-observation availability; a limited fit need not provide a table. |
API reference and further reading
| Task | R API | Result |
|---|---|---|
| Compile | compile_model(source) |
Session-local native model pointer |
| Simulate | simulate_model(model, request) |
Native result as an R list |
| Tabulate a simulation | result_data_frame(result) |
Long data and identity attributes |
| Individual, pooled or population fit | fit_model(model, request) |
Inspect fit or population status |
| Parameter scan | scan_model(model, request) |
Per-point simulation results |
| Morris screening | morris_model(model, request) |
Design and elementary effects |
| Prepare dose and measurement rows | nonmem_requests(...) |
Per-subject requests and source data |
| Simulate prepared rows | simulate_data(model, dataset) |
Subject results and combined data |
| Prepare individual or pooled fit | fit_data_requests(...) |
Fit requests and source mapping |
| Execute prepared fits | fit_data(model, prepared) |
Fit results and mappings |
| Tabulate estimates | fit_parameter_data_frame(fitted) |
Estimates, units, statuses and objective |
| Replay fitted observations | fit_observation_data_frame(model, fitted) |
Converged-fit observation diagnostics |
| Prepare population rows | population_fit_data_requests(...) |
One population request and mapping |
| Fit population rows | fit_population_data(model, prepared) |
Population result and mapping |
| Tabulate population subjects | population_fit_subject_data_frame(fitted) |
Eta and individual parameter values |
| Tabulate population observations | population_fit_observation_data_frame(fitted) |
Fitted-observation diagnostics or NULL |
Installed help is available through ?compile_model, ?nonmem_requests,
?fit_data_requests, and ?population_fit_data_requests. Read the
model language, regimen contract,
run and result identity,
parameter scans, Morris screening,
Gaussian objectives, and JSON schemas
when adapting examples. For Python colleagues, use
the Python user guide.