|
| 1 | +#' Steady state by pseudo-transient continuation |
| 2 | +#' |
| 3 | +#' Solves `f(x, p) = 0` for variables of a [cppFUN()] model, one output per |
| 4 | +#' solved variable, optionally under linear constraints `C x = total`. |
| 5 | +#' |
| 6 | +#' Each step solves `(I/dt - J) d = f` together with the constraint rows in the |
| 7 | +#' least-squares sense. Small `dt` gives implicit Euler steps along |
| 8 | +#' `dx/dt = f`, large `dt` Newton steps; `dt` follows the local error of the |
| 9 | +#' pseudo-time step, so the iteration follows the flow to a stable steady |
| 10 | +#' state and converges quadratically near it. With `flow = FALSE` the steps |
| 11 | +#' follow the Newton flow and any regular root of plain equations is returned. |
| 12 | +#' |
| 13 | +#' @param fun A compiled [cppFUN()] object with forward derivatives. |
| 14 | +#' @param x Named numeric, values of all variables of `fun`. The solved |
| 15 | +#' variables start there, the others keep their values. |
| 16 | +#' @param parms Named numeric, values of the parameters of `fun`. |
| 17 | +#' @param solve Names of the variables to solve for. Default all. |
| 18 | +#' @param rows Names of the outputs of `fun` paired with `solve`, in the same |
| 19 | +#' order. Default `solve`. |
| 20 | +#' @param C,total Constraint matrix with columns named by `solve`, and its |
| 21 | +#' right-hand side: `C x = total`. Default none. |
| 22 | +#' @param flow `TRUE` if the outputs are the right-hand side of an ODE, |
| 23 | +#' `FALSE` for plain equations. |
| 24 | +#' @param positive Keep the solved variables positive: the step is taken |
| 25 | +#' multiplicatively and the constraints are restored by `x exp(C' lambda)`. |
| 26 | +#' @param controls Named list: `rtol` (default `1e-10`) and `atol` (`1e-14`), |
| 27 | +#' every row within `rtol` of its turnover `sum_k |df_i/dx_k| |x_k|` plus |
| 28 | +#' `atol` times its diagonal; `flowTol` (`1`), relative local error of a |
| 29 | +#' pseudo-time step; `maxit` (`400`); `dtInit` (`1e-2`), the first |
| 30 | +#' pseudo-time step, relative to the fastest rate if `flow = TRUE`; `zmax` |
| 31 | +#' (`5`), the largest log change of a variable per step. |
| 32 | +#' @return A list with the named solution `x` (all variables), `converged`, |
| 33 | +#' `iterations` and `message`. |
| 34 | +#' @references Kelley CT, Keyes DE (1998). Convergence analysis of |
| 35 | +#' pseudo-transient continuation. SIAM J Numer Anal 35(2):508-523. |
| 36 | +#' @example inst/examples/ptc.R |
| 37 | +#' @export |
| 38 | +ptc <- function(fun, x, parms, solve = names(x), rows = solve, C = NULL, total = NULL, |
| 39 | + flow = TRUE, positive = TRUE, controls = list()) { |
| 40 | + vars <- attr(fun, "variables"); pars <- attr(fun, "parameters") |
| 41 | + outs <- names(attr(fun, "equations")); mn <- attr(fun, "modelname") |
| 42 | + if (is.null(vars) || is.null(mn)) stop("'fun' must be a cppFUN() object") |
| 43 | + if (!"forward" %in% attr(fun, "derivMode")) stop("'fun' needs forward derivatives") |
| 44 | + if (!all(vars %in% names(x))) stop("'x' misses ", toString(setdiff(vars, names(x)))) |
| 45 | + if (!all(pars %in% names(parms))) stop("'parms' misses ", toString(setdiff(pars, names(parms)))) |
| 46 | + if (!all(solve %in% vars)) stop("unknown variables in 'solve': ", toString(setdiff(solve, vars))) |
| 47 | + if (length(rows) != length(solve) || !all(rows %in% outs)) |
| 48 | + stop("'rows' must name one output of 'fun' per solved variable") |
| 49 | + if (is.null(C)) { |
| 50 | + C <- matrix(0, 0, length(solve)); total <- numeric(0) |
| 51 | + } else { |
| 52 | + if (!all(solve %in% colnames(C))) stop("'C' needs a column per solved variable") |
| 53 | + C <- C[, solve, drop = FALSE] |
| 54 | + if (length(total) != nrow(C)) stop("'total' needs one value per row of 'C'") |
| 55 | + } |
| 56 | + def <- list(rtol = 1e-10, atol = 1e-14, flowTol = 1, maxit = 400L, dtInit = 1e-2, zmax = 5) |
| 57 | + bad <- setdiff(names(controls), names(def)) |
| 58 | + if (length(bad)) stop("unknown controls: ", toString(bad)) |
| 59 | + ctrl <- utils::modifyList(def, as.list(controls)) |
| 60 | + ctrl$flow <- as.numeric(isTRUE(flow)); ctrl$positive <- as.numeric(isTRUE(positive)) |
| 61 | + ctrl <- lapply(ctrl, as.double) |
| 62 | + x0 <- as.double(x[vars]) |
| 63 | + if (isTRUE(positive) && any(x0[match(solve, vars)] <= 0)) |
| 64 | + stop("with positive = TRUE the solved variables must start positive") |
| 65 | + |
| 66 | + sym <- .nativeSym(paste0(mn, "_eval_ad")) |
| 67 | + if (is.null(sym)) stop("compile '", mn, "' first") |
| 68 | + addr <- getNativeSymbolInfo(sym$name, PACKAGE = sym$dll)$address |
| 69 | + res <- .Call(C_cppde_ptc, addr, length(outs), x0, as.double(parms[pars]), |
| 70 | + as.integer(match(solve, vars) - 1L), as.integer(match(rows, outs) - 1L), |
| 71 | + matrix(as.double(C), nrow(C)), as.double(total), ctrl) |
| 72 | + xo <- setNames(x0, vars); xo[solve] <- res$x |
| 73 | + list(x = xo, converged = res$converged, iterations = res$iterations, message = res$message) |
| 74 | +} |
0 commit comments