--- title: "Using minimaxApprox" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Using minimaxApprox} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") ``` ```{r setup, echo = FALSE} library(minimaxApprox) # nolint undesireable_function_linter ``` ## What is minimax approximation? Least-squares fitting minimizes the *average* squared error; interpolation forces the fit through chosen points and lets the error do whatever it likes in between. Minimax approximation asks a different question: what is the polynomial (or rational function) of a given degree whose **largest** error anywhere on the interval is as small as possible? The answer has a distinctive fingerprint: at the optimum, the error curve oscillates between its extreme positive and negative values at least `degree + 2` times (`degree1 + degree2 + 2` for rational approximation), a property called *equioscillation*. Below, a degree-5 minimax fit to `sin(x)` on `[0, 1]`, plotted with `plot.minimaxApprox`: ```{r, fig.width = 6, fig.height = 4} fit <- minimaxApprox(sin, 0, 1, 5) plot(fit) ``` The error touches its bounds seven times, alternating sign every time---no other degree-5 polynomial can do better in the worst case. The exchange algorithm used to find such a fit dates to Remez (1934; the translation cited in `?minimaxApprox` is Remez, 1962), refined for rational approximation by Fraser and Hart (1962) and Cody, Fraser, and Hart (1968) into the form this package follows most closely. A newer *barycentric* formulation---used here as an additional basis option---represents the trial function by its values rather than by coefficients, following Pachón and Trefethen (2009) for polynomials and Filip, Nakatsukasa, Trefethen, and Beckermann (2018) for rational functions; see **Choosing a basis** below. ## Quick start ```{r} fit <- minimaxApprox(exp, 0, 1, 5) fit ``` `coef()` extracts the coefficient vectors: ```{r} coef(fit) ``` `a` holds the coefficients in whatever basis was used to fit (Chebyshev by default); `aMono` is always the monomial-basis (raw `x^k`) equivalent, useful when you need ordinary polynomial coefficients regardless of how the fit was computed. `minimaxEval()` evaluates the fit at new points, using the same basis as the fit unless told otherwise: ```{r} x <- seq(0, 1, length.out = 11) minimaxEval(x, fit) ``` In the printed summary, `ExpectedAbsError` is the leveled error the Remez iteration converged to; `ObservedAbsError` is the largest error actually seen at the final reference points. `Ratio` and `Difference` are the two ways the two are compared for convergence (see **Options** below); for a clean converged result they will be at or near 1 and 0 respectively. ## Choosing a basis Three bases are available via the `basis` argument, matched case-insensitively and by abbreviation (`"m"`, `"c"`, `"b"`). **Monomial** (`x^k`) is the most familiar and the worst conditioned: the Vandermonde-style linear solve it relies on degrades quickly with degree or with intervals far from `[-1, 1]`. **Chebyshev** (the default) evaluates `T_k` on the range **mapped** to `[-1, 1]`; off that interval, `a` are coefficients of `T_k` in the *mapped* variable, not raw `x` (a breaking change as of 0.6.0; see `NEWS.Rd`). `aMono` is always in raw `x` regardless of basis, recovered by composing the mapped-Chebyshev coefficients with the affine map; that composition is itself a monomial-basis operation, so `aMono` inherits some of the conditioning cost of the monomial basis at high degree on wide, far-off-range intervals. **Barycentric** represents the trial function by its values at the reference points rather than by coefficients, so there is no linear solve to go singular. It is the most numerically robust choice and converges in cases the classical bases cannot reach. For example, on `[10, 20]` at degree 30: ```{r, error = TRUE} tryCatch(minimaxApprox(exp, 10, 20, 30, basis = "monomial"), error = function(e) message(conditionMessage(e))) ``` ```{r} bfit <- minimaxApprox(exp, 10, 20, 30, basis = "barycentric") bfit$ObsErr bfit$Warning ``` Monomial hard-errors; barycentric converges cleanly. (Chebyshev also manages this case, but only via the machine-precision fallback described in **Trust and diagnostics** below, at a coarser error than barycentric achieves here.) For best evaluation accuracy, use `minimaxEval()` on the barycentric object directly---it evaluates the stored barycentric representation, which is more accurate than the derived monomial coefficients. The one potentially lossy step is the `aMono` conversion; `convResid` reports how much accuracy that conversion cost: ```{r} bfit$convResid ``` ## Rational approximation Rational approximation fits a ratio of two polynomials, requested as `degree = c(numerator, denominator)`. It is most useful when the function has a nearby singularity (in the real or complex plane) that a polynomial can only chase by adding more and more degree. Compare a polynomial and a rational fit to a function with a pole just outside `[-1, 1]`: ```{r} f <- function(x) 1 / (x + 1.05) poly8 <- minimaxApprox(f, -1, 1, 8) poly8$ObsErr # degree 8, still poor rat21 <- minimaxApprox(f, -1, 1, c(2, 1)) # classical (Cody) rational rat21$ObsErr ``` The degree-(2,1) rational is many orders of magnitude more accurate than the degree-8 polynomial. The barycentric basis handles the same request via the adaptive algorithm of Filip, Nakatsukasa, Trefethen, and Beckermann (2018): ```{r} rat21b <- minimaxApprox(f, -1, 1, c(2, 1), basis = "barycentric") rat21b$ObsErr rat21b$Warning ``` The barycentric rational implementation in this release is a deliberate subset of the published algorithm, with three limitations. It supports **absolute error only**; requesting `relErr = TRUE` with `basis = "b"` is an error. Degenerate or *defective* requests---for example, an even numerator and denominator degree for an even function, which has fewer effective parameters than the requested degree pair---are detected and reported as an error naming a workaround, rather than repaired by degree reduction: ```{r, error = TRUE} g <- function(x) 1 / (x ^ 2 + 0.01) tryCatch(minimaxApprox(g, -1, 1, c(3, 3), basis = "barycentric"), error = function(e) message(conditionMessage(e))) ``` And a trial denominator with a zero inside the approximation interval is likewise reported as an error, mirroring the classical rational path. ## Relative error By default, `minimaxApprox()` minimizes *absolute* error. Setting `relErr = TRUE` instead minimizes relative error, `(fn(x) - approx(x)) / fn(x)`, useful when the function's magnitude varies enormously over the interval and a uniform absolute tolerance would waste accuracy where the function is small and starve it where the function is large: ```{r} relfit <- minimaxApprox(exp, 0.5, 2, 6, relErr = TRUE) relfit$ObsErr # this is now a maximum *relative* error, not absolute ``` Relative error is undefined wherever `fn` is exactly 0, so an interval whose endpoints (or interior, more generally) include a zero of `fn` cannot be fit with `relErr = TRUE`: ```{r, error = TRUE} tryCatch(minimaxApprox(sin, -1, 1, 5, relErr = TRUE), error = function(e) message(conditionMessage(e))) ``` ## Trust and diagnostics Every returned object carries a `Warning` flag. `Warning = TRUE` means one of two things happened: the iteration did not converge to the requested tolerance within `maxiter` (see **Options** below), or---on every approximation path, as of 0.6.0---the returned result's true maximum error was checked against a dense evaluation grid and found to materially exceed the leveled error the algorithm reported. In the second case, `ExpErr` is still a valid *lower bound* on the true minimax error, not a certificate of it. Because of this, a non-converged result's `ObsErr` need not bound the true maximum error---the final reference points may not track the approximation's actual extrema. Verify with `minimaxErr()` on a dense grid before relying on such a result: ```{r} chk <- minimaxApprox(exp, 5, 6, 10) chk$Warning chk$ObsErr # what the object reports grid <- seq(5, 6, length.out = 100001) max(abs(minimaxErr(grid, chk))) # what a dense grid shows ``` Here the two are close but not identical---exactly the kind of gap the `Warning` flag exists to surface. ## Options Most calls need no `opts` at all. Two are worth knowing: `maxiter` (default 100) caps the number of Remez iterations. If a fit hits this cap and warns of non-convergence, increasing `maxiter` occasionally resolves it, though a persistent warning at high `maxiter` more often signals a genuinely hard case (near machine precision, or non-normal for the function---see **Trust and diagnostics**) than one that simply needs more iterations. `convrat` and `tol` control how closely the observed and expected error must agree before the algorithm declares convergence---by ratio (`convrat`, default `1 + 1e-9`) or by absolute difference (`tol`, default `1e-14`), whichever check is more informative for the current error magnitude. Loosening either lets marginal cases converge sooner, at the cost of a less tightly verified result. `tailtol` and `ztol` also exist but do not apply to `basis = "b"`, which has no coefficient solve to restart or prune. ## References Remez, E. I. (1962) *General computational methods of Chebyshev approximation: The problems with linear real parameters*. US Atomic Energy Commission, Division of Technical Information. AEC-tr-4491 Fraser, W. and Hart, J. F. (1962) "On the computation of rational approximations to continuous functions", *Communications of the ACM*, 5(7), 401-403. \doi{10.1145/368273.368578} Cody, W. J., Fraser, W. and Hart, J. F. (1968) "Rational Chebyshev approximation using linear equations", *Numerische Mathematik*, 12, 242-251. \doi{10.1007/BF02162506} Pachón, R. and Trefethen, L. N. (2009) "Barycentric-Remez algorithms for best polynomial approximation in the chebfun system", *BIT Numerical Mathematics*, 49(4), 721-741. \doi{10.1007/s10543-009-0240-1} Berrut, J.-P. and Trefethen, L. N. (2004) "Barycentric Lagrange interpolation", *SIAM Review*, 46(3), 501-517. \doi{10.1137/S0036144502417715} Higham, N. J. (2004) "The numerical stability of barycentric Lagrange interpolation", *IMA Journal of Numerical Analysis*, 24(4), 547-556. \doi{10.1093/imanum/24.4.547} Filip, S.-I., Nakatsukasa, Y., Trefethen, L. N. and Beckermann, B. (2018) "Rational minimax approximation via adaptive barycentric representations", *SIAM Journal on Scientific Computing*, 40(4), A2427-A2455. \doi{10.1137/17M1132409} Nakatsukasa, Y., Sète, O. and Trefethen, L. N. (2018) "The AAA algorithm for rational approximation", *SIAM Journal on Scientific Computing*, 40(3), A1494-A1522. \doi{10.1137/16M1106122}