Skip to contents

This developer article maps the implemented drmTMB model paths to the files that define, test, and document them. It is written for contributors who need to make a small, reviewable change without rediscovering the whole package.

The source map is not a roadmap. It records what currently exists. Planned features belong in docs/design/06-distribution-roadmap.md, docs/design/09-phylogenetic-and-spatial-speed.md, and related design notes until implementation, tests, and documentation all exist.

Routing overview

All fitted models enter through drmTMB() in R/drmTMB.R. The family router dispatches to one of the implemented builders:

gaussian()                  -> drm_build_gaussian_ls_spec()
student()                   -> drm_build_student_ls_spec()
lognormal()                 -> drm_build_lognormal_ls_spec()
Gamma(link = "log")         -> drm_build_gamma_ls_spec()
beta()                      -> drm_build_beta_ls_spec()
beta_binomial()             -> drm_build_beta_binomial_spec()
cumulative_logit()          -> drm_build_cumulative_logit_spec()
poisson(link = "log")       -> drm_build_poisson_spec()
poisson(link = "log") + zi  -> drm_build_poisson_spec()
nbinom2()                   -> drm_build_nbinom2_spec()
nbinom2() + zi              -> drm_build_nbinom2_spec()
truncated_nbinom2()         -> drm_build_truncated_nbinom2_spec()
truncated_nbinom2() + hu    -> drm_build_truncated_nbinom2_spec()
biv_gaussian()              -> drm_build_biv_gaussian_spec()
c(gaussian(), gaussian())   -> drm_build_biv_gaussian_spec()
list(gaussian(), gaussian()) -> drm_build_biv_gaussian_spec()

The R builders assemble model frames, design matrices, starting values, random effect metadata, known covariance objects, and the TMB data list. The current TMB template then uses these integer model paths:

model_type = 1   univariate Gaussian location-scale engine
model_type = 2   bivariate Gaussian location-scale-coscale engine
model_type = 3   univariate Student-t location-scale-shape engine
model_type = 4   univariate lognormal location-scale engine
model_type = 5   univariate Gamma mean-CV engine
model_type = 6   univariate Poisson mean engine
model_type = 7   univariate negative-binomial 2 mean-dispersion engine
model_type = 8   univariate zero-inflated Poisson engine
model_type = 9   univariate zero-inflated negative-binomial 2 engine
model_type = 10  univariate beta mean-scale engine
model_type = 11  univariate zero-truncated negative-binomial 2 engine
model_type = 12  univariate hurdle negative-binomial 2 engine
model_type = 13  univariate cumulative-logit ordinal engine
model_type = 14  univariate beta-binomial mean-overdispersion engine
model_type = 99  internal phylogenetic prior helper used by tests

The model_type = 99 path is not a user-facing family. It lets tests compare the hidden phylogenetic precision prior used inside the Gaussian engine against the same objective in isolation.

Post-fit response-scale transforms live in R/methods.R. predict() delegates implemented distributional-parameter links to drm_dpar_link() and drm_inverse_link(), while fitted() delegates family-specific response summaries to drm_fitted_response(). These helpers keep future non-identity mu families from silently inheriting Gaussian assumptions.

Likelihood weights are a model-fitting option rather than formula grammar. drmTMB(..., weights = w) evaluates one non-negative row multiplier per modelled row, stores the processed vector in fit$model$weights, exposes it through weights(fit), and passes it to TMB as DATA_VECTOR(weights). meta_V(V = V) remains the preferred route for known sampling variance or covariance; meta_known_V(V = V) is the compatibility alias.

Implemented paths

Path User-facing syntax R builder and helpers TMB branch Main tests Main docs
Gaussian location-scale drm_formula(y ~ x, sigma ~ z) drm_build_gaussian_ls_spec() in R/drmTMB.R; generic family object from stats::gaussian() model_type = 1 in src/drmTMB.cpp tests/testthat/test-gaussian-location-scale.R, tests/testthat/test-comparators.R vignettes/location-scale.Rmd, docs/design/13-gaussian-location-scale-math.md
Gaussian location random effects y ~ x + (1 \| id); y ~ x + (1 + x \| id); labelled form (1 + x \| p \| id) extract_random_mu_terms() and build_random_mu_structure() inside the Gaussian builder model_type = 1; u_mu, log_sd_mu, and eta_cor_mu blocks tests/testthat/test-gaussian-random-intercepts.R, tests/testthat/test-comparators.R docs/design/04-random-effects.md, vignettes/formula-grammar.Rmd
Gaussian residual scale random effects sigma ~ z + (1 \| id) extract_random_sigma_terms() and build_random_sigma_structure() inside the Gaussian builder model_type = 1; u_sigma and log_sd_sigma blocks tests/testthat/test-gaussian-random-intercepts.R docs/design/04-random-effects.md, vignettes/which-scale.Rmd
Gaussian random-effect scale models sd(id) ~ w; sd(site) ~ site_type parse_sd_mu_entries() and build_sd_mu_structure() inside the Gaussian builder model_type = 1; X_sd_mu, beta_sd_mu, and mu_re_sd_row tests/testthat/test-gaussian-random-effect-scale.R, tests/testthat/test-comparators.R docs/design/18-random-effect-scale-models.md, docs/design/13-gaussian-location-scale-math.md
Gaussian meta-analysis with known sampling covariance yi ~ x + meta_V(V = vi) or meta_V(V = V); meta_known_V(V = V) remains a compatibility alias meta_V() and meta_known_V() in R/formula-markers.R; evaluate_known_v() and subset_known_v() in R/drmTMB.R model_type = 1; diagonal path adds V_known, dense path uses density::MVNORM; dense V is reported by check_drm() as small-to-moderate storage tests/testthat/test-meta-known-v.R, tests/testthat/test-comparators.R, tests/testthat/test-check-drm.R vignettes/meta-analysis.Rmd, docs/design/08-meta-analysis.md
Gaussian phylogenetic location effect phylo(1 \| species, tree = tree) in the mu formula phylo() marker and helpers in R/phylo-utils.R; build_phylo_mu_structure() inside the Gaussian builder model_type = 1; u_phylo, log_sd_phylo, sparse Q_phylo tests/testthat/test-phylo-gaussian.R, tests/testthat/test-phylo-utils.R vignettes/phylogenetic-spatial.Rmd, docs/design/09-phylogenetic-and-spatial-speed.md
Gaussian spatial location effect spatial(1 \| site, coords = coords) or one numeric spatial(1 + x \| site, coords = coords) slope in the mu formula spatial() marker and coordinate helpers inside R/drmTMB.R; build_spatial_mu_structure() reuses the structured precision backend and stores coefficient-specific design values model_type = 1; u_phylo and log_sd_phylo internally, labelled as spatial_mu, spatial(1 \| site), and spatial(0 + x \| site) in R output tests/testthat/test-spatial-gaussian.R vignettes/phylogenetic-spatial.Rmd, docs/design/09-phylogenetic-and-spatial-speed.md, docs/design/16-phylo-spatial-common-math.md
Likelihood row weights drmTMB(..., weights = w) evaluate_likelihood_weights_arg() and subset_likelihood_weights() in R/drmTMB.R; weights.drmTMB() in R/methods.R DATA_VECTOR(weights) multiplies independent row contributions; full dense known-covariance blocks reject non-unit weights tests/testthat/test-gaussian-location-scale.R, tests/testthat/test-biv-gaussian.R docs/design/22-likelihood-weights.md, docs/design/03-likelihoods.md
Student-t location-scale-shape drm_formula(y ~ x, sigma ~ z, nu ~ w) student() in R/family.R; drm_build_student_ls_spec() in R/drmTMB.R model_type = 3; nu = 2 + exp(eta_nu) tests/testthat/test-student-location-scale.R vignettes/robust-student.Rmd, docs/design/14-gamlss-parameter-names.md
Lognormal location-scale drm_formula(biomass ~ x, sigma ~ z) lognormal() in R/family.R; drm_build_lognormal_ls_spec() in R/drmTMB.R model_type = 4; log(y) ~ Normal(mu, sigma^2) with the log-Jacobian term tests/testthat/test-lognormal-location-scale.R vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md
Gamma mean-CV drm_formula(biomass ~ x, sigma ~ z) with family = Gamma(link = "log") drm_build_gamma_ls_spec() in R/drmTMB.R; generic family object from stats::Gamma(link = "log") model_type = 5; shape = 1 / sigma^2, scale = mu * sigma^2 tests/testthat/test-gamma-location-scale.R vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Beta mean-scale drm_formula(prop ~ x, sigma ~ z) with family = beta() beta() in R/family.R; drm_build_beta_ls_spec() in R/drmTMB.R model_type = 10; mu = logit^{-1}(eta_mu), phi = 1 / sigma^2, alpha = mu * phi, beta_shape = (1 - mu) * phi tests/testthat/test-beta-location-scale.R vignettes/proportion-beta-binomial.Rmd, vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Beta-binomial mean-overdispersion drm_formula(cbind(successes, failures) ~ x, sigma ~ z) with family = beta_binomial() beta_binomial() in R/family.R; drm_build_beta_binomial_spec() in R/drmTMB.R model_type = 14; mu = logit^{-1}(eta_mu), phi = 1 / sigma^2, successes follow a beta-binomial distribution with known trial totals tests/testthat/test-beta-binomial.R vignettes/proportion-beta-binomial.Rmd, vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Cumulative-logit ordinal location drm_formula(score ~ x) with family = cumulative_logit() cumulative_logit() in R/family.R; drm_build_cumulative_logit_spec() in R/drmTMB.R model_type = 13; Pr(y_i <= k) = logit^{-1}(theta_k - mu_i) with ordered cutpoints and fixed latent logistic scale tests/testthat/test-cumulative-logit.R vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Poisson mean drm_formula(count ~ x + offset(log(effort))) with family = poisson(link = "log") drm_build_poisson_spec() in R/drmTMB.R; generic family object from stats::poisson(link = "log") model_type = 6; y ~ Poisson(mu) with mu = exp(offset + X beta) tests/testthat/test-poisson-mean.R vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Zero-inflated Poisson drm_formula(count ~ x + offset(log(effort)), zi ~ z) with family = poisson(link = "log") drm_build_poisson_spec() in R/drmTMB.R; zi formula adds the structural-zero block model_type = 8; Pr(y = 0) = zi + (1 - zi) exp(-mu) and Pr(y > 0) = (1 - zi) Poisson(y \| mu) tests/testthat/test-zi-poisson.R vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Negative-binomial 2 mean-dispersion drm_formula(count ~ x + (1 \| id) + (0 + x \| id), sigma ~ z) with family = nbinom2() nbinom2() in R/family.R; drm_build_nbinom2_spec() in R/drmTMB.R model_type = 7; mu = exp(offset + X beta + Z b), Var(y) = mu + sigma^2 * mu^2, size = 1 / sigma^2; ordinary mu random intercepts and independent numeric slopes use u_mu and log_sd_mu; shared NB2 count-kernel helper tests/testthat/test-nbinom2-location-scale.R, tests/testthat/test-count-kernels.R, tests/testthat/test-comparators.R vignettes/count-nbinom2.Rmd, vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Zero-inflated negative-binomial 2 drm_formula(count ~ x + offset(log(effort)), sigma ~ z, zi ~ w) with family = nbinom2() nbinom2() in R/family.R; drm_build_nbinom2_spec() in R/drmTMB.R; zi formula adds the structural-zero block model_type = 9; count component mu = exp(offset + X beta), Var(y) = mu + sigma^2 * mu^2, Pr(y = 0) = zi + (1 - zi) NB2(0 \| mu, sigma), and Pr(y > 0) = (1 - zi) NB2(y \| mu, sigma); shared NB2 count-kernel helper tests/testthat/test-zi-nbinom2.R, tests/testthat/test-count-kernels.R vignettes/count-nbinom2.Rmd, vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Zero-truncated negative-binomial 2 drm_formula(count ~ x, sigma ~ z) with family = truncated_nbinom2() truncated_nbinom2() in R/family.R; drm_build_truncated_nbinom2_spec() in R/drmTMB.R model_type = 11; Pr_trunc(y) = Pr_NB2(y) / (1 - Pr_NB2(0)); fitted returns mu / (1 - Pr_NB2(0)); shared NB2 count-kernel helper tests/testthat/test-truncated-nbinom2-location-scale.R, tests/testthat/test-count-kernels.R vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Hurdle negative-binomial 2 drm_formula(count ~ x, sigma ~ z, hu ~ w) with family = truncated_nbinom2() truncated_nbinom2() in R/family.R; drm_build_truncated_nbinom2_spec() in R/drmTMB.R; hu formula adds the hurdle-zero block model_type = 12; Pr(y = 0) = hu; Pr(y > 0) = (1 - hu) Pr_trunc(y); fitted returns (1 - hu) * mu / (1 - Pr_NB2(0)); shared NB2 count-kernel helper tests/testthat/test-hurdle-nbinom2.R, tests/testthat/test-count-kernels.R vignettes/distribution-families.Rmd, docs/design/02-family-registry.md, docs/design/03-likelihoods.md, docs/design/19-family-link-contract.md
Bivariate Gaussian location-coscale mu1 = y1 ~ x; mu2 = y2 ~ x; sigma1 ~ z1; sigma2 ~ z2; rho12 ~ w; optional matching (1 \| p \| id) random intercepts in mu1/mu2, sigma1/sigma2, one same-response mu/sigma pair, or the all-four mu1/mu2/sigma1/sigma2 block biv_gaussian() in R/family.R; drm_build_biv_gaussian_spec() in R/drmTMB.R; rho12() and corpairs() in R/methods.R model_type = 2; rho12 = 0.99999999 * tanh(eta_rho12); bivariate group-level correlations use eta_cor_mu for mu1/mu2, eta_cor_sigma for sigma1/sigma2, eta_cor_mu_sigma for same-response mean-scale pairs, and u_re_cov for all-four q=4 blocks tests/testthat/test-biv-gaussian.R vignettes/bivariate-coscale.Rmd, docs/design/15-location-coscale-phylogenetic-extension.md, docs/design/20-coscale-correlation-pairs.md
Bivariate Gaussian known sampling covariance meta_V(V = V) in one bivariate location formula, with V a row-paired 2n by 2n matrix; meta_known_V(V = V) remains a compatibility alias meta_vcov_bivariate() in R/meta-vcov.R; evaluate_biv_known_v() and subset_biv_known_v() in R/drmTMB.R model_type = 2; dense V_known_matrix plus fitted residual sigma1, sigma2, and rho12; dense storage is diagnostic-visible but not a large-data claim tests/testthat/test-biv-gaussian.R, tests/testthat/test-meta-vcov.R, tests/testthat/test-check-drm.R vignettes/meta-analysis.Rmd, vignettes/testing-likelihoods.Rmd

What the source map protects

When a feature changes, update all rows touched by that feature. A change to the Student-t likelihood, for example, should be checked against:

R/family.R
R/drmTMB.R
src/drmTMB.cpp
tests/testthat/test-student-location-scale.R
vignettes/robust-student.Rmd
vignettes/testing-likelihoods.Rmd
vignettes/adding-families.Rmd
docs/design/03-likelihoods.md
docs/design/14-gamlss-parameter-names.md

A change to residual bivariate correlation rho12 should be checked against:

R/family.R
R/drmTMB.R
R/methods.R
src/drmTMB.cpp
tests/testthat/test-biv-gaussian.R
vignettes/bivariate-coscale.Rmd
vignettes/formula-grammar.Rmd
vignettes/testing-likelihoods.Rmd
docs/design/03-likelihoods.md
docs/design/15-location-coscale-phylogenetic-extension.md

The goal is consistency. The symbolic equations, R syntax, TMB branch, tests, vignettes, and check log should make the same claim.

C++ modularization boundary

The C++ template is still one compiled TMB entry point. The modularization plan in docs/design/36-cpp-modularization-source-map.md should be treated as the source of truth before moving code out of src/drmTMB.cpp. Its first pass moves only pure numeric and likelihood-kernel helpers into headers; it does not move DATA_* declarations, PARAMETER_* declarations, REPORT() calls, ADREPORT() calls, R builders, formula grammar, or public branch IDs.

Validation-debt register

The stable-core matrix in the README and model-map article is backed by docs/design/34-validation-debt-register.md. That register records whether each advertised surface is covered, partial, opt-in, or blocked, and it names the tests, diagnostics, interval route, docs, and explicit debt for each row. When a surface moves from planned or first-slice status to routine support, update the register with the implementation, tests, docs, NEWS, check-log evidence, and after-task report in the same pull request.

The public status words are the reader-facing version of that internal ledger. Stable maps to a covered routine path, first slice maps to a covered but narrow fitted path, opt-in control maps to scalability or memory hardening, and planned, reserved, unsupported, or blocked rows stay out of runnable analysis examples until code, tests, docs, and after-task evidence exist.

Current boundaries

The following neighbouring features are intentionally not implemented as fitted models yet:

  • mixed composed families such as c(gaussian(), poisson());
  • the future meta_V() umbrella, including proportional sampling-variance models such as meta_V(value, w = w, scale = "proportional");
  • bivariate Student-t or bivariate skew-normal families;
  • bivariate random slopes, rho12 random effects, and cross-parameter covariance blocks beyond the currently implemented ordinary and phylogenetic random-intercept slices;
  • mesh/SPDE spatial fields, bivariate spatial covariance blocks, spatial scale models, multiple spatial slopes, slope correlations, and spatial corpair() regressions;
  • univariate phylogenetic or spatial effects in sigma, nu, or rho12, plus spatial effects outside the first univariate Gaussian mu intercept and one-slope coordinate paths;
  • derived profile-likelihood intervals for q=4 and other indirect covariance summaries beyond direct profile-ready targets.

Do not present those forms as runnable examples until the source map has an implemented row with code, tests, and documentation.

One neighbouring combination now has both a targeted validation test and a careful tutorial explanation: Gaussian known-covariance meta-analysis with sd(group) ~ predictors. The test checks the fitted objective against an independent dense marginal Gaussian likelihood. The tutorial explains that sigma is residual heterogeneity and sd(group) is group-level heterogeneity in a random effect.