Solves for one unknown scalar argument of an existing actuarial or financial function. The remaining arguments are supplied through `args`, and the unknown is chosen with `solve_for`.
Usage
solve_value(
fn,
solve_for,
target,
args = list(),
result = NULL,
interval = NULL,
start = NULL,
derivative = NULL,
method = c("auto", "uniroot", "newton"),
multiple = c("error", "all", "first"),
tol = 1e-10,
maxiter = 1000L,
scan_points = 401L,
max_expand = 6L,
output = c("value", "summary", "audit"),
residual_tol = tol
)Arguments
- fn
A function, or the name of a function, whose scalar result is to be matched to `target`.
- solve_for
Character scalar. Name of the scalar argument to solve for.
- target
Finite numeric scalar. Desired value of the selected result.
- args
Named list containing all known arguments passed to `fn`. Do not supply a non-`NULL` value for the argument named in `solve_for`.
- result
Optional result extractor. Use `NULL` when `fn` returns a numeric scalar; a character scalar naming a column or list element; or a function that extracts a numeric scalar from the object returned by `fn`.
- interval
Optional finite numeric vector of length 2. It defines the admissible search interval. When omitted, a conservative interval is inferred from `solve_for`, `target`, and the numeric values in `args`.
- start
Optional finite numeric scalar used as the initial value for Newton–Raphson. It is also used as a fallback starting point when `method = "auto"` cannot bracket a root.
- derivative
Optional function of one argument returning the derivative of the equation residual with respect to the unknown. When omitted under Newton–Raphson, a central finite-difference derivative is used.
- method
Character scalar. One of `"auto"`, `"uniroot"`, or `"newton"`. The default first searches for bracketed roots and uses Newton–Raphson only when no bracket is found and `start` is available.
- multiple
Character scalar controlling multiple roots found inside the interval: `"error"`, `"all"`, or `"first"`.
- tol
Positive finite numeric scalar controlling unknown-coordinate precision: passed to `uniroot`, used relatively for Newton corrections, and used when identifying duplicate roots.
- maxiter
Positive integer. Maximum number of iterations.
- scan_points
Positive integer. Number of points used to detect sign changes and possible multiple roots.
- max_expand
Nonnegative integer. Maximum number of automatic interval expansions when an interval was inferred and a bracket is not found initially. An interval supplied explicitly by the user is never expanded.
- output
Character scalar. One of `"value"`, `"summary"`, or `"audit"`.
- residual_tol
Positive finite numeric scalar. Absolute residual tolerance in the units of the selected result of `fn`. Defaults to `tol`.
Value
Depending on `output`:
`"value"`: numeric scalar or vector containing the solution(s).
`"summary"`: compact tibble with the unknown, solution, target, achieved value, residual, and method.
`"audit"`: detailed tibble with convergence diagnostics and search information.
Details
The equation solved is $$g(x) = f(\ldots, x, \ldots) - \mathrm{target} = 0,$$ where `f` is the function supplied in `fn` and `x` is the argument named in `solve_for`.
This function is intended to reuse the stable calculation functions already available in tidyactuarial. It does not reproduce their actuarial formulas.
Newton iteration accepts an exact zero immediately. Otherwise, convergence
requires both a small residual and a small Newton correction relative to
the unknown: abs(dx) <= tol * max(1, abs(x)). The residual must
satisfy abs(achieved - target) <= residual_tol.
A nonzero derivative is not rejected merely
because its units make its absolute magnitude small. A step clipped to an
interval boundary does not by itself demonstrate convergence.
The audit field `converged` requires successful solver termination and the explicit residual tolerance, without a hidden numerical floor. Exhausting `uniroot` iterations is an error. A bracketed approximation can satisfy coordinate precision while failing residual precision; `"value"` and `"summary"` still return that candidate, so use `"audit"` to inspect this distinction. Newton retains its error when it cannot meet both criteria.
When changing the units of `fn` and `target` by a positive factor, scale `residual_tol` by the same factor and retain `tol`. For example, a root in years with a value in currency has a temporal coordinate tolerance and a monetary residual tolerance. No automatic scale is inferred from `target`.
During interval scanning, only exact computed zeros are accepted directly as grid roots. Other candidates require a sign-changing bracket and are refined numerically. A small residual alone does not establish a root; tangential roots may require an appropriate Newton starting value.
`solve_value()` is restricted to one scalar unknown. It is appropriate for quantities such as an effective interest rate, a payment, a principal, a present value, or another continuous scalar parameter.
Bracketed root finding is preferred because it is generally more stable than an unconstrained Newton iteration. The interval is scanned before solving; therefore, possible multiple roots can be detected. This is important for non-monotone equations of value.
Discrete unknowns, such as an integer number of payments, should not be solved by pretending they are continuous. A dedicated discrete solver or a model-specific wrapper should be used for those cases.
See also
[stats::uniroot()], [irr_flow()], [irr_flow_multi()], [bond_ytm()], [present_value()], [a_angle()]
Other time-value:
accumulation_factor(),
discount_factor(),
future_value(),
fv_flow(),
irr_flow(),
irr_flow_multi(),
plot_cash_flow(),
present_value(),
pv_flow()
Examples
# Solve for the annual effective rate
solve_value(
fn = present_value,
solve_for = "i",
target = 8000,
args = list(
C = 10000,
t = 5,
i_type = "effective"
),
interval = c(0, 0.20)
)
#> [1] 0.04563955
# Solve for a level annuity payment. Because a_angle(tidy = TRUE)
# returns a tibble, select the present_value column explicitly.
solve_value(
fn = a_angle,
solve_for = "payment",
target = 100000,
args = list(
n = 10,
i = 0.05,
timing = "immediate",
tidy = TRUE
),
result = "present_value",
interval = c(0, 20000),
output = "summary"
)
#> # A tibble: 1 × 6
#> solve_for solution target achieved residual method
#> <chr> <dbl> <dbl> <dbl> <dbl> <chr>
#> 1 payment 12950. 100000 100000 0 uniroot
# Newton--Raphson for a simple custom equation
solve_value(
fn = function(x) x^2,
solve_for = "x",
target = 2,
start = 1,
interval = c(0, 2),
method = "newton"
)
#> [1] 1.414214
# A term in years, allowing one cent of monetary residual
solve_value(
fn = function(t) 1000 * 1.05^t,
solve_for = "t", target = 1500, interval = c(0, 20),
tol = 1e-10, residual_tol = 0.01, output = "audit"
)
#> # A tibble: 1 × 15
#> root_id function_name solve_for solution target achieved residual method
#> <int> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr>
#> 1 1 function(t) 1000 *… t 8.31 1500 1500. 2.40e-10 uniro…
#> # ℹ 7 more variables: converged <lgl>, n_iter <int>, interval_left <dbl>,
#> # interval_right <dbl>, interval_inferred <lgl>, n_expansions <int>,
#> # n_roots <int>