## ----include = FALSE----------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
options(rmarkdown.html_vignette.check_title = FALSE)

## ----setup, include = FALSE---------------------------------------------------
library(qfratio)
require(stats)
set.seed(764561)

## ----definition_exported, eval = FALSE----------------------------------------
#  pqfr <- function(quantile, A, B, p = 1, mu = rep.int(0, n), Sigma = diag(n),
#                   lower.tail = TRUE, log.p = FALSE,
#                   method = c("imhof", "davies", "forchini", "butler"),
#                   trim_values = TRUE, return_abserr_attr = FALSE, m = 100L,
#                   tol_zero = .Machine$double.eps * 100,
#                   tol_sing = tol_zero, ...) { ... }
#  dqfr <- function(quantile, A, B, p = 1, mu = rep.int(0, n), Sigma = diag(n),
#                   log = FALSE, method = c("broda", "hillier", "butler"),
#                   trim_values = TRUE, normalize_spa = FALSE,
#                   return_abserr_attr = FALSE, m = 100L,
#                   tol_zero = .Machine$double.eps * 100,
#                   tol_sing = tol_zero, ...) { ... }

## ----definition_qqfr, eval = FALSE--------------------------------------------
#  qqfr <- function(probability, A, B, p = 1, mu = rep.int(0, n), Sigma = diag(n),
#                   lower.tail = TRUE, log.p = FALSE, trim_values = FALSE,
#                   return_abserr_attr = FALSE, stop_on_error = FALSE, m = 100L,
#                   tol_zero = .Machine$double.eps * 100,
#                   tol_sing = tol_zero, epsabs_q = .Machine$double.eps ^ (1/2),
#                   maxiter_q = 5000, ...) { ... }

## ----example_methods, error = TRUE--------------------------------------------
## Choice from alternative methods
A <- diag(1:3)
pqfr(1.5, A, method = "imhof")    # default
pqfr(1.5, A, method = "davies")   # similar
pqfr(1.5, A, method = "forchini") # series
pqfr(1.5, A, method = "butler")   # spa

dqfr(1.5, A, method = "broda")    # default
dqfr(1.5, A, method = "hillier")  # series
dqfr(1.5, A, method = "butler")   # spa

## Not recommended; for diagnostic use only
qfratio:::pqfr_imhof(1.5, A)
qfratio:::pqfr_A1B1(1.5, A, m = 9, check_convergence = FALSE)


## This is okay
x <- c(1.5, 2.5, 3.5)
pqfr(x, A)

## This is not
qfratio:::pqfr_imhof(x, A)

## ----ks_syntax_correct--------------------------------------------------------
## Small Monte Carlo sample
A <- diag(1:3)
B <- diag(sqrt(1:3))
x <- rqfr(10, A, B)

## Calculate p-values
pseq <- pqfr(x, A, B, return_abserr_attr = TRUE)

## Maximum error when evaluated at x;
## looks small enough
max(attr(pseq, "abserr"))

## Correct syntax, expected outcome
## \(q) syntax could also be used in recent versions of R
ks.test(x, function(q) pqfr(q, A, B))

## ----ks_syntax_wrong----------------------------------------------------------
## Incorrect; no error/warning because
## B is passed to ks.test rather than to pqfr
ks.test(x, pqfr, A = A, B = B)

## ----definition_internal_series, eval = FALSE---------------------------------
#  ## Used in pqfr(..., method = "forchini")
#  pqfr_A1B1 <- function(quantile, A, B, m = 100L, mu = rep.int(0, n),
#                        check_convergence = c("relative", "strict_relative",
#                                              "absolute", "none"),
#                        stop_on_error = FALSE, use_cpp = TRUE,
#                        cpp_method = c("double", "long_double", "coef_wise"),
#                        nthreads = 1,
#                        tol_conv = .Machine$double.eps ^ (1/4),
#                        tol_zero = .Machine$double.eps * 100,
#                        tol_sing = tol_zero,
#                        thr_margin = 100) { ... }
#  ## Used in dqfr(..., method = "hillier")
#  dqfr_A1I1 <- function(quantile, LA, m = 100L,
#                        check_convergence = c("relative", "strict_relative",
#                                              "absolute", "none"),
#                        use_cpp = TRUE,
#                        tol_conv = .Machine$double.eps ^ (1/4),
#                        thr_margin = 100) { ... }

## ----example_series_1, error = TRUE-------------------------------------------
A <- diag(1:3)
pqfr(1.5, A, method = "forchini")
dqfr(1.5, A, method = "hillier")

B <- diag(sqrt(1:3))
pqfr(1.5, A, B, method = "forchini")
## dqfr method does not accommodate B, mu, or Sigma
dqfr(1.5, A, B, method = "hillier")

## ----example_series_2, error = TRUE-------------------------------------------
A <- diag(1:3)

## p-value just below 2, an eigenvalue of A
## Typically throws two warnings:
##   Maximum iteration in hypergeometric function
##   and non-convergence of series
pqfr(1.9999, A, method = "forchini")

## More realistic value; expected from symmetry
pqfr(1.9999, A, method = "imhof")

## ----definition_internal_inversion, eval = FALSE------------------------------
#  ## Used in pqfr(..., method = "imhof") (default)
#  pqfr_imhof <- function(quantile, A, B, mu = rep.int(0, n),
#                         autoscale_args = 1, stop_on_error = TRUE, use_cpp = TRUE,
#                         tol_zero = .Machine$double.eps * 100,
#                         epsabs = epsrel, epsrel = 1e-6, limit = 1e4) { ... }
#  ## Used in pqfr(..., method = "davies")
#  pqfr_davies <- function(quantile, A, B, mu = rep.int(0, n),
#                          autoscale_args = 1,
#                          tol_zero = .Machine$double.eps * 100, ...) { ... }
#  ## Used in dqfr(..., method = "broda") (default)
#  dqfr_broda <- function(quantile, A, B, mu = rep.int(0, n),
#                         autoscale_args = 1, stop_on_error = TRUE,
#                         use_cpp = TRUE, tol_zero = .Machine$double.eps * 100,
#                         epsabs = epsrel, epsrel = 1e-6, limit = 1e4) { ... }

## ----example_specify_error----------------------------------------------------
A <- diag(1:4)

## This error bound satisfies "abserr < value * epsrel"
pqfr(3.9, A, method = "imhof", return_abserr_attr = TRUE,
     epsabs = 0, epsrel = 1e-6)

## This one violates "abserr < value * epsrel",
## although abserr is a valid error bound
pqfr(1.2, A, method = "imhof", return_abserr_attr = TRUE,
     epsabs = 0, epsrel = 1e-6)


## ----example_inversion_scale, error = TRUE------------------------------------
A <- diag(1:3)
B <- diag(sqrt(1:3))

## Without autoscale_args
## We know these are equal
pqfr(1.5, A, B, autoscale_args = FALSE)
pqfr(1.5, A * 1e-10, B * 1e-10, autoscale_args = FALSE)
## The latter failed because of numerically small eigenvalues

## With autoscale_args = 1 (default)
pqfr(1.5, A * 1e-10, B * 1e-10)

## ----example_inversion_trim, error = TRUE-------------------------------------
## Result without trimming;
## (typically) negative density, which is absurd
## In this case, error interval typically spans across 0
dqfr(1.2, diag(1:30), return_abserr_attr = TRUE,
     trim_values = FALSE)

## Result with trimming (default)
dqfr(1.2, diag(1:30), return_abserr_attr = TRUE)
## Note that the actual value is only bounded by
## 0 and abserr

## ----definition_internal_spa, eval = FALSE------------------------------------
#  ## Used in pqfr(..., method = "butler")
#  pqfr_butler <- function(quantile, A, B, mu = rep.int(0, n),
#                          order_spa = 2, stop_on_error = FALSE, use_cpp = TRUE,
#                          tol_zero = .Machine$double.eps * 100,
#                          epsabs = .Machine$double.eps ^ (1/2), epsrel = 0,
#                          maxiter = 5000) { ... }
#  ## Used in dqfr(..., method = "butler")
#  dqfr_butler <- function(quantile, A, B, mu = rep.int(0, n),
#                          order_spa = 2, stop_on_error = FALSE, use_cpp = TRUE,
#                          tol_zero = .Machine$double.eps * 100,
#                          epsabs = .Machine$double.eps ^ (1/2), epsrel = 0,
#                          maxiter = 5000) { ... }

## ----example_spa_1------------------------------------------------------------
A <- diag(1:3)

## Default for spa distribution function
pqfr(1.2, A, method = "butler", order_spa = 2)

## First-order spa
pqfr(1.2, A, method = "butler", order_spa = 1)

## More accurate numerical inversion
pqfr(1.2, A)


## Default for density
dqfr(1.2, A, method = "butler",
     order_spa = 2, normalize_spa = FALSE)

## First-order
dqfr(1.2, A, method = "butler",
     order_spa = 1, normalize_spa = FALSE)

## Normalized density, second-order
dqfr(1.2, A, method = "butler",
     order_spa = 2, normalize_spa = TRUE)

## Normalized density, first-order
dqfr(1.2, A, method = "butler",
     order_spa = 1, normalize_spa = TRUE)

## More accurate numerical inversion
dqfr(1.2, A)


## ----errorbound_1-------------------------------------------------------------
A <- diag(1:4)

pqfr(1.5, A, return_abserr_attr = TRUE)

dqfr(1.5, A, return_abserr_attr = TRUE)

## ----errorbound_trim, error = TRUE--------------------------------------------
## Without trimming, result is (typically) negative
## But note that value + abserr is positive
dqfr(1.2, diag(1:35), return_abserr_attr = TRUE,
     epsabs = 1e-10, trim_values = FALSE)

## With trimming, value is replaced by tol_zero
## Note slightly shortened abserr
dqfr(1.2, diag(1:35), return_abserr_attr = TRUE,
     epsabs = 1e-10)


## When untrimmed value + abserr < tol_zero
dqfr(1.1, diag(1:35), return_abserr_attr = TRUE,
     epsabs = 1e-15, trim_values = FALSE)
## True value is somewhere between 0 and value + abserr
## (assuming these are reliable)

## When trimmed, abserr reflects tol_zero
## because the true value is between 0 and tol_zero
dqfr(1.1, diag(1:35), return_abserr_attr = TRUE,
     epsabs = 1e-15)

## ----errorbound_q1------------------------------------------------------------
A <- diag(1:4)

qqfr(0.95, A, return_abserr_attr = TRUE)

## ----errorbound_q-------------------------------------------------------------
qqfr(0, A, return_abserr_attr = TRUE)

## ----example_profile_distr, fig.width = 4, figh.height = 4, error = TRUE------
A <- diag(1:4)
qseq <- seq.int(0.8, 4.2, length.out = 100)

## Generate p-value sequences
## Warning is expected
pseq_inv <- pqfr(qseq, A, method = "imhof",
                 return_abserr_attr = TRUE)
pseq_ser <- pqfr(qseq, A, method = "forchini",
                 check_convergence = FALSE)
pseq_spa <- pqfr(qseq, A, method = "butler")

## Maximum error in numerical inversion;
## looks small enough
max(attr(pseq_inv, "abserr"))

## Graphical comparison
par(mar = c(4, 4, 0.1, 0.1))
plot(qseq, type = "n", xlim = c(1, 4), ylim = c(0, 1),
     xlab = "q", ylab = "F(q)")
lines(qseq, pseq_inv, col = "gray", lty = 1)
lines(qseq, pseq_ser, col = "tomato", lty = 2)
lines(qseq, pseq_spa, col = "slateblue", lty = 3)
legend("topleft", legend = c("inversion", "series", "saddlepoint"),
       col = c("gray", "tomato", "slateblue"), lty = 1:3, cex = 0.8)

## Logical vector to exclude q around eigenvalues of A
avoid_evals <- ((qseq %% 1) > 0.05) & ((qseq %% 1) < 0.95)

## Numerical comparison
all.equal(pseq_inv[avoid_evals], pseq_ser[avoid_evals],
          check.attributes = FALSE)
all.equal(pseq_inv[avoid_evals], pseq_spa[avoid_evals],
          check.attributes = FALSE)

## ----example_profile_density, fig.width = 4, figh.height = 4, error = TRUE----
## Generate p-value sequences
dseq_inv <- dqfr(qseq, A, method = "broda",
                 return_abserr_attr = TRUE)
dseq_ser <- dqfr(qseq, A, method = "hillier",
                 check_convergence = FALSE)
dseq_spa <- dqfr(qseq, A, method = "butler")

## Maximum error in numerical inversion;
## looks small enough
max(attr(dseq_inv, "abserr"))

## Graphical comparison
par(mar = c(4, 4, 0.1, 0.1))
plot(qseq, type = "n", xlim = c(1, 4), ylim = c(0, 0.8),
     xlab = "q", ylab = "f(q)")
lines(qseq, dseq_inv, col = "gray", lty = 1)
lines(qseq, dseq_ser, col = "tomato", lty = 2)
lines(qseq, dseq_spa, col = "slateblue", lty = 3)
legend("topleft", legend = c("inversion", "series", "saddlepoint"),
       col = c("gray", "tomato", "slateblue"), lty = 1:3, cex = 0.8)

## Numerical comparison
all.equal(dseq_inv, dseq_ser, check.attributes = FALSE)
all.equal(dseq_inv, dseq_spa, check.attributes = FALSE)

## Do densities sum up to 1?
sum(dseq_inv * diff(qseq)[1])
sum(dseq_ser * diff(qseq)[1])
sum(dseq_spa * diff(qseq)[1])

## ----example_density_normalize, fig.width = 4, figh.height = 4, error = TRUE----
## Normalized saddlepoint approximation density
dseq_spa_normalized <- dqfr(qseq, A, method = "butler",
                            normalize_spa = TRUE)
all.equal(dseq_inv, dseq_spa_normalized,
          check.attributes = FALSE)
sum(dseq_spa_normalized * diff(qseq)[1])

