--- title: "Multiple integration over convex polyhedra" output: rmarkdown::html_vignette: css: vignette.css vignette: > %\VignetteIndexEntry{Multiple integration over convex polyhedra} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` This package allows to evaluate multiple integrals like: $$ \int_{-5}^4\int_{-5}^{3-x}\int_{-10}^{6-x-y} f(x,y,z) \,\mathrm{d}z\,\mathrm{d}y\,\mathrm{d}x $$ Using base R only, a possibility is to nest the `integrate` function to evaluate such an integral: ```{r} f <- function(x, y, z) x*(x+1) - y*z^2 integrate(Vectorize(function(x) { integrate(Vectorize(function(y) { integrate(function(z) { f(x,y,z) }, -10, 6 - x - y)$value }), -5, 3 - x)$value }), -5, 4) ``` This approach works well in general. But it has one default: the estimate of the absolute error it returns is not reliable, because the estimates of the absolute errors of the inner integrals are not taken into account. Here is how to proceed with the **polyhedralCubature** package. The domain of integration is defined by the set of inequalities: $$ \left\{\begin{matrix} -5 & \leq & x & \leq & 4 \\ -5 & \leq & y & \leq & 3-x \\ -10 & \leq & z & \leq & 6-x-y \end{matrix} \right. $$ which is equivalent to $$ \left\{\begin{matrix} -x & \leq & 5 \\ x & \leq & 4 \\ -y & \leq & 5 \\ x+y & \leq & 3 \\ -z & \leq & 10 \\ x+y+z & \leq & 6 \end{matrix} \right.. $$ This set of inequalities defines a convex polyhedron. In order to use **polyhedralCubature**, you have to construct the matrix `A` defining the linear combinations of the variables, and the vector `b` giving the upper bounds of these linear combinations: ```{r Ab} A <- rbind( c(-1, 0, 0), # -x c( 1, 0, 0), # x c( 0,-1, 0), # -y c( 1, 1, 0), # x+y c( 0, 0,-1), # -z c( 1, 1, 1) # x+y+z ) b <- c(5, 4, 5, 3, 10, 6) ``` Then you can use the `integrateOverPolyhedron` function: ```{r integrate_function} library(polyhedralCubature) f <- function(x, y, z) { x*(x+1) - y*z^2 } integrateOverPolyhedron(f, A, b) ``` Alternatively, you can use the `getAb` function to get `A` and `b` with the help of the user-friendly syntax of the **ompr** package: ```{r getAb} library(ompr) model <- MIPModel() %>% add_variable(x) %>% add_variable(y) %>% add_variable(z) %>% add_constraint(-5 <= x) %>% add_constraint(x <= 4) %>% add_constraint(-5 <= y) %>% add_constraint(y <= 3 - x) %>% add_constraint(-10 <= z) %>% add_constraint(z <= 6 - x - y) getAb(model) ``` Observe that the function $f$ is a polynomial here. Then one can get the exact value of the integral by feeding `integrateOverPolyhedron` with a **spray** polynomial instead of a function: ```{r integrate_spray, message=FALSE} library(spray) x <- lone(1, 3); y <- lone(2, 3); z <- lone(3, 3) p <- f(x, y, z) integrateOverPolyhedron(p, A, b) ``` The first step in `integrateOverPolyhedron` consists in finding the vertices of the polyhedron from the set of linear inequalities. It is possible, and better, to use exact arithmetic for this step, by defining the matrix `A` and the vector `b` in character mode, each entry representing an integer or a fraction like `"1/3"`. Here we can simply do: ```{r character_mode} storage.mode(A) <- "character" storage.mode(b) <- "character" ``` But this is not the way to use if you have a fraction like `1/3` in an entry: ```{r as_character_fraction} as.character(1/3) ``` Instead, you must directly define `A` and `b` in character mode and enter `"1/3"` for this entry. Finally, when $f$ is a polynomial, you can get the exact value of the integral given as a fraction, by using a **qspray** polynomial instead of a **spray** polynomial: ```{r integrate_qspray} library(qspray) x <- qlone(1); y <- qlone(2); z <- qlone(3) q <- f(x, y, z) integrateOverPolyhedron(q, A, b) ```