# tests/testthat/test-nonlinear_models.R # ---- fixtures ------------------------------------------------------------- make_logistic <- function(n = 60, seed = 1) { set.seed(seed) days <- seq(5, 100, length.out = n) bm <- 500 / (1 + exp((45 - days) / 8)) + rnorm(n, 0, 10) data.frame(days = days, biomass_g = pmax(bm, 0)) } make_asymptotic <- function(n = 50, seed = 2) { set.seed(seed) n_rate <- seq(0, 200, length.out = n) yield <- 6 * (1 - exp(-0.015 * n_rate)) + rnorm(n, 0, 0.15) data.frame(nitrogen = n_rate, yield = pmax(yield, 0)) } make_quadratic <- function(n = 40, seed = 3) { set.seed(seed) x <- seq(0, 200, length.out = n) y <- 2 + 0.04 * x - 0.0001 * x^2 + rnorm(n, 0, 0.1) data.frame(nitrogen = x, yield = y) } make_lp <- function(n = 60, seed = 4) { set.seed(seed) x <- seq(0, 200, length.out = n) cp <- 120 y <- ifelse(x < cp, 2 + 0.025 * x, 2 + 0.025 * cp) + rnorm(n, 0, 0.05) data.frame(nitrogen = x, yield = y) } make_gompertz <- function(n = 60, seed = 5) { set.seed(seed) days <- seq(1, 100, length.out = n) bm <- 500 * exp(-exp(-0.08 * (days - 40))) + rnorm(n, 0, 5) data.frame(days = days, biomass_g = pmax(bm, 0)) } # ---- fit_nonlinear — basic ------------------------------------------------ test_that("fit_nonlinear logistic returns agriNLS", { dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") expect_s3_class(m, "agriNLS") expect_equal(m$model, "logistic") }) test_that("fit_nonlinear asymptotic converges", { dat <- make_asymptotic() m <- fit_nonlinear(dat, "nitrogen", "yield", "asymptotic") expect_s3_class(m, "agriNLS") }) test_that("fit_nonlinear quadratic converges", { dat <- make_quadratic() m <- fit_nonlinear(dat, "nitrogen", "yield", "quadratic") expect_s3_class(m, "agriNLS") }) test_that("fit_nonlinear linear_plateau converges", { dat <- make_lp() m <- fit_nonlinear(dat, "nitrogen", "yield", "linear_plateau", start = list(a = 2, b = 0.025, cp = 120)) expect_s3_class(m, "agriNLS") }) test_that("fit_nonlinear gompertz converges", { dat <- make_gompertz() m <- tryCatch( fit_nonlinear(dat, "days", "biomass_g", "gompertz", start = list(Asym = 500, b2 = -1, b3 = 40)), error = function(e) NULL ) skip_if(is.null(m), "Gompertz did not converge on this platform -- skipped") expect_s3_class(m, "agriNLS") }) # ---- coef / fitted / residuals / predict ---------------------------------- test_that("coef.agriNLS returns named vector", { dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") cf <- coef(m) expect_true(all(c("Asym", "xmid", "scal") %in% names(cf))) }) test_that("fitted.agriNLS returns correct length", { dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") expect_length(fitted(m), nrow(dat)) }) test_that("residuals.agriNLS returns correct length", { dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") expect_length(residuals(m), nrow(dat)) }) test_that("predict.agriNLS works with new data", { dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") nd <- data.frame(days = c(30, 60, 90)) pr <- predict(m, newdata = nd) expect_length(pr, 3) }) # ---- print / summary ------------------------------------------------------ test_that("print.agriNLS produces agriReg header", { dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") expect_output(print(m), "agriReg") }) test_that("summary.agriNLS shows goodness-of-fit", { dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") expect_output(summary(m), "Goodness-of-fit") }) # ---- plot ----------------------------------------------------------------- test_that("plot.agriNLS returns ggplot", { skip_if_not_installed("ggplot2") dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") p <- plot(m) expect_s3_class(p, "gg") }) test_that("plot.agriNLS with show_residuals=TRUE returns ggplot", { skip_if_not_installed("ggplot2") dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") p <- plot(m, show_residuals = TRUE) expect_s3_class(p, "gg") }) # ---- optimum_dose --------------------------------------------------------- test_that("optimum_dose quadratic returns named vector", { dat <- make_quadratic() m <- fit_nonlinear(dat, "nitrogen", "yield", "quadratic") opt <- optimum_dose(m) expect_named(opt, c("x_opt", "y_max")) expect_true(opt["x_opt"] > 0) }) test_that("optimum_dose linear_plateau returns cp as optimum", { dat <- make_lp() m <- fit_nonlinear(dat, "nitrogen", "yield", "linear_plateau", start = list(a = 2, b = 0.025, cp = 120)) opt <- optimum_dose(m) expect_named(opt, c("x_opt", "y_max")) # cp should be near 120 expect_lt(abs(opt["x_opt"] - 120), 20) }) test_that("optimum_dose errors for unsupported model", { dat <- make_logistic() m <- fit_nonlinear(dat, "days", "biomass_g", "logistic") expect_error(optimum_dose(m), "only supports") }) # ---- custom start values -------------------------------------------------- test_that("fit_nonlinear accepts user start values", { dat <- make_logistic() start <- list(Asym = 480, xmid = 44, scal = 8) m <- fit_nonlinear(dat, "days", "biomass_g", "logistic", start = start) expect_s3_class(m, "agriNLS") })