--- title: "Linear transformations and matrix inverse in 3D" author: "Michael Friendly" date: "`r Sys.Date()`" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Linear transformations and matrix inverse in 3D} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r nomessages, echo = FALSE} knitr::opts_chunk$set( warning = FALSE, message = FALSE, echo = TRUE, collapse = TRUE, fig.height = 5, fig.width = 5 ) options(digits=4) par(mar=c(4,4,1,1)+.1) ``` ```{r setuprgl, echo=FALSE} library(rgl) library(knitr) knit_hooks$set(webgl = hook_webgl) ``` ```{r} library(matlib) # use the package library(rgl) # also using rgl ``` This vignette illustrates how linear transformations can be represented in 3D using the [`rgl` package](https://dmurdoch.github.io/rgl/). It is explained more fully in this StackExchange discussion [3D geometric interpretations of matrix inverse](https://math.stackexchange.com/questions/1948184/3d-geometric-interpretations-of-matrix-inverse). It also illustrates the determinant, $\det(\mathbf{A})$, as the volume of the transformed cube, and the relationship between $\mathbf{A}$ and $\mathbf{A}^{-1}$. ## The unit cube Start with a unit cube, which represents the identity matrix $\mathbf{I}_{3 \times 3}$ = `diag(3)` in 3 dimensions. In `rgl` this is given by `cube3d()` which returns a `"mesh3d"` object containing the vertices from -1 to 1 on each axis. I translate and scale this to make each vertex go from 0 to 1. These are represented in homogeneous coordinates to handle perspective and transformations. See `help("identityMatrix")` for details. ```{r unit-cube} library(rgl) library(matlib) # cube, with each face colored differently colors <- rep(2:7, each=4) c3d <- cube3d() # make it a unit cube at the origin c3d <- scale3d(translate3d(c3d, 1, 1, 1), .5, .5, .5) str(c3d) ``` The vertices are the 8 columns of the `vb` component of the object. ```{r vertices} c3d$vb ``` I want to show the transformation of $\mathbf{I}$ by a matrix $\mathbf{A}$ as the corresponding transformation of the cube. ```{r matrix-A} # matrix A: A <- matrix(c( 1, 0, 1, 0, 2, 0, 1, 0, 2), 3, 3) |> print() det(A) # A can be produced by elementary row operations on the identity matrix I <- diag( 3 ) AA <- I |> rowadd(3, 1, 1) |> # add 1 x row 3 to row 1 rowadd(1, 3, 1) |> # add 1 x row 1 to row 3 rowmult(2, 2) # multiply row 2 by 2 all(AA==A) ``` ## Define some useful functions I want to be able to display 3D mesh objects, with colored points, lines and faces. This is a little tricky, because in rgl colors are applied to vertices, not faces. The faces are colored by interpolating the colors at the vertices. See the StackOverflow discussion [drawing a cube with colored faces, vertex points and lines](https://stackoverflow.com/questions/39730889/rgl-drawing-a-cube-with-colored-faces-vertex-points-and-lines). The function `draw3d()` handles this for `"mesh3d"` objects. Another function, `vlabels()` allows for labeling vertices. ```{r draw3d} # draw a mesh3d object with vertex points and lines draw3d <- function(object, col=rep(rainbow(6), each=4), alpha=0.6, vertices=TRUE, lines=TRUE, reverse = FALSE, ...) { if(reverse) col <- rev(col) shade3d(object, col=col, alpha=alpha, ...) vert <- t(object$vb) indices <- object$ib if (vertices) points3d(vert, size=5) if (lines) { for (i in 1:ncol(indices)) lines3d(vert[indices[,i],]) } } # label vertex points vlabels <- function(object, vertices, labels=vertices, ...) { text3d( t(object$vb[1:3, vertices] * 1.05), texts=labels, ...) } ``` ## Show $\mathbf{I}$ and $\mathbf{A}$ all together in one figure We can show the cube representing the identity matrix $\mathbf{I}$ using `draw3d()`. Transforming this by $\mathbf{A}$ is accomplished using `transform3d()`. ```{r 3d-demo1, webgl=TRUE} open3d() draw3d(c3d) vlabels(c3d, c(1,2,3,5)) axes <- rbind( diag(3), -diag(3) ) rownames(axes) <- c("x", "y", "z", rep(" ", 3)) vectors3d(axes, frac.lab=1.2, headlength = 0.2, radius=1/20, lwd=3) c3t<- transform3d(c3d, A) draw3d(c3t) vlabels(c3t, c(1,2,3,5)) ``` ## Same, but using separate figures, shown side by side ```{r 3d-demo2, webgl=TRUE} # NB: this scales each one separately, so can't see relative size open3d() mfrow3d(1,2, sharedMouse=TRUE) draw3d(c3d) vectors3d(axes, frac.lab=1.2, headlength = 0.2, radius=1/20, lwd=3) next3d() draw3d(c3t) vectors3d(axes, frac.lab=1.2, headlength = 0.2, radius=1/20, lwd=3) ``` ## $A$ and $A^{-1}$ The inverse of `A` is found using `solve() ```{r AI} AI <- solve(A) |> print() ``` The determinant of the matrix `A` here is 2. The determinant of $\mathbf{A}^{-1}$ is the reciprocal, $1/ \det(\mathbf{A})$. Geometrically, this means that the larger $\mathbf{A}^{-1}$ is the smaller is $\mathbf{A}^{-1}$. ```{r det-AI} det(A) det(AI) ``` You can see the relation between them by graphing both together in the same plot. It is clear that $\mathbf{A}^{-1}$ is small in the directions $\mathbf{A}$ is large, and vice versa. ```{r 3d-demo3, webgl=TRUE} open3d() draw3d(c3t) c3Inv <- transform3d(c3d, solve(A)) draw3d(c3Inv) vectors3d(axes, frac.lab=1.2, headlength = 0.2, radius=1/20, lwd=3) vlabels(c3t, 8, "A", cex=1.5) vlabels(c3t, c(2,3,5)) vlabels(c3Inv, 4, "Inv", cex=1.5) vlabels(c3Inv, c(2,3,5)) ``` ## Animate The `rgl` graphic can be animated by spinning it around an axis using `spin3d()`. This can be played on screen using `play3d()` or saved to a `.gif` file using `movie3d()`. ```{r 3d-demo4, webgl=TRUE} play3d(spin3d(rpm=15), duration=4) #movie3d(spin3d(rpm=15), duration=4, movie="inv-demo", dir=".") ```