On a Möbius transformation
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Consider a complex number
γ such that
|γ|<1 and the following
matrix:
M=(iγˉγ−i).
Then the Möbius transformation associated to this matrix is nice. Why? Because:
it maps the unit disk to itself;
-
it is of order 2;
its fractional powers have a closed form.
For these reasons, I often use this Möbius transformation in my shaders.
Let us derive the fractional powers of M. We set h=√1−|γ|2.
The eigenvalues of M are
λ1=−ihλ2=ih=¯λ1
In particular, Mt is given by
(abˉbˉa)
M_power_t <- function(gamma, t){ h <- sqrt(1-Mod(gamma)^2) d2 <- h^t * (cos(t*pi/2) + 1i*sin(t*pi/2)) d1 <- Conj(d2) a <- Re(d1) - 1i*Im(d1)/h b <- gamma * Im(d2)/h c <- Conj(b) d <- Conj(a) c(a = a, b = b, c = c, d = d) }
Let’s apply this Möbius transformation now. Here is a visualization of the Dedekind eta function, a complex function availale in the jacobi package:
# background color bkgcol <- rgb(21, 25, 30, maxColorValue = 255) modulo <- function(a, p) { a - p * ifelse(a > 0, floor(a/p), ceiling(a/p)) } colormap <- function(z){ if(is.na(z)){ return(bkgcol) } if(is.infinite(z) || is.nan(z)){ return("#000000") } x <- Re(z) y <- Im(z) r <- modulo(Mod(z), 1) g <- 2 * abs(modulo(atan2(y, x), 0.5)) b <- abs(modulo(x*y, 1)) if(is.nan(b)){ return("#000000") } rgb( 8 * (1 - cos(r-0.5)), 8 * (1 - cos(g-0.5)), 8 * (1 - cos(b-0.5)), maxColorValue = 1 ) } library(jacobi) f <- Vectorize(function(x, y){ q <- x + 1i*y if(Mod(q) > 0.9999 || (Im(q) == 0 && Re(q) <= 0)){ return(bkgcol) } tau <- -1i * log(q) / pi z <- eta(tau) colormap(z) }) x <- y <- seq(-1, 1, len = 2000) image <- outer(x, y, f) opar <- par(mar = c(0,0,0,0), bg = bkgcol) plot( c(-100, 100), c(-100, 100), type = "n", xlab = "", ylab = "", axes = FALSE, asp = 1 ) rasterImage(image, -100, -100, 100, 100) par(opar)
Here is how to apply the Möbius transformation for one value of the power t:
Mobius <- M_power_t(gamma = 0.7 - 0.3i, t = ...) a <- Mobius["a"] b <- Mobius["b"] c <- Mobius["c"] d <- Mobius["d"]; f <- Vectorize(function(x, y){ q0 <- x + 1i*y q <- (a*q0 + b) / (c*q0 + d) if(Mod(q) > 0.9999 || (Im(q) == 0 && Re(q) <= 0)){ return(bkgcol) } tau <- -1i * log(q) / pi z <- eta(tau) colormap(z) }) x <- y <- seq(-1, 1, len = 2000) image <- outer(x, y, f)
Then it suffices to run this code for t varying from 0 to 2, and to save the image for each value of t. But this would be very slow. Actually I implemented the image generation with Rcpp. Here is the result:
My Rcpp code is available in the Github version of the jacobi package. The R code which generates an image for one value of t is:
x <- seq(-1, 1, len = 2000L) gamma <- 0.7 - 0.3i t <- ... image <- jacobi:::Image_eta(x, gamma, t) opar <- par(mar = c(0,0,0,0), bg = bkgcol) plot( c(-100, 100), c(-100, 100), type = "n", xlab = "", ylab = "", axes = FALSE, asp = 1 ) rasterImage(image, -100, -100, 100, 100) par(opar)
You can also play with jacobi:::Image_E4
and
jacobi:::Image_E6
, which respectively generate a
visualization of the Eisenstein series of weight
4 and a visualization of the
Eisenstein series of weight 6.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.