Exponential grids / mobius transformations

Hi jc2046,

Maths are not my strong point, so take the following with caution; going by the formula at the top of the Wikipedia entry on Mobius transformation, the code might look like

Complex mobius(Complex a, Complex b, Complex c, Complex d, 
  Complex z) {
  return mobius(a, b, c, d, z, new Complex());
}

Complex mobius(Complex a, Complex b, Complex c, Complex d, 
  Complex z, Complex out) {
  return div(add(mult(a, z), b), add(mult(c, z), d), out);
}

There are all sorts of implementations for a complex number in Java; you can reference this Stack Overflow discussion for developed and tested implementations. For now, a basic class

class Complex {
  float real = 0.0;
  float imaginary = 0.0;

  Complex() {
  }

  Complex(float real, float imaginary) {
    set(real, imaginary);
  }

  String toString() {
    return String.format("(%.2f, %.2f)", real, imaginary);
  }

  Complex clone() {
    return new Complex(real, imaginary);
  }

  Complex set(float real, float imaginary) {
    this.real = real;
    this.imaginary = imaginary;
    return this;
  }
}

with some basic operations (+, -, *, /):

Complex add(Complex a, Complex b) {
  return add(a, b, new Complex());
}

Complex add(Complex a, Complex b, Complex out) {
  return out.set(a.real + b.real, 
    a.imaginary + b.imaginary);
}

Complex div(Complex a, Complex b) {
  return div(a, b, new Complex());
}

Complex div(Complex a, Complex b, Complex out) {
  return mult(a, reciprocal(b), out);
}

Complex mult(Complex a, Complex b) {
  return mult(a, b, new Complex());
}

Complex mult(Complex a, Complex b, Complex out) {
  return out.set(
    a.real * b.real - a.imaginary * b.imaginary, 
    a.real * b.imaginary + a.imaginary * b.real);
}

Complex reciprocal(Complex a) {
  return reciprocal(a, new Complex());
}

Complex reciprocal(Complex a, Complex out) {
  final float modulusSq = a.real * a.real + a.imaginary * a.imaginary;
  return out.set(a.real / modulusSq, -a.imaginary / modulusSq);
}

Complex sub(Complex a, Complex b) {
  return sub(a, b, new Complex());
}

Complex sub(Complex a, Complex b, Complex out) {
  return out.set(a.real - b.real, 
    a.imaginary - b.imaginary);
}

(static functions are not available to classes created within the Processing IDE, so I just placed the above under draw in the main sketch. You could also create instance methods for in-place operations a += b, a -= b, a *= b, a /= b .)

Messing around with the four inputs to the formula (a, b, c, d and e) gave me the following
complex

// Number of iterations of mobius transformation.
int transformations = 20;

// Smoothness of convex polygons approximating a circle.
int detail = 200;

// For display.
float x, y, scale, animIncr = 1 / 30.0;

// Four inputs to mobius function.
Complex a = new Complex(1.0, 0.0);
Complex b = new Complex(0.0, 0.0);
Complex c = new Complex(0.0, 0.0);
Complex d = new Complex(1.0, 0.0);

// Reference circle.
Complex[] circle = new Complex[detail];

// Transformations.
Complex[][] transformed = new Complex[transformations][detail];

void setup() {
  size(512, 256);
  smooth(8);
  noFill();
  strokeWeight(1.5);
  strokeCap(ROUND);
  strokeJoin(ROUND);

  // Translate and scale circles
  // to make them fit in the sketch's center.
  x = width * 0.5;
  y = height * 0.5;
  scale = min(width, height) * 0.25;

  // Calculate points on reference circle.
  // The y-axis represents imaginary numbers.
  float iToTheta = TWO_PI / float(detail);
  for (int i = 0; i < detail; ++i) {
    float theta = i * iToTheta;
    circle[i] = new Complex(cos(theta), sin(theta));
  }

  // Create transformations.
  float jToStep = 1.0 / float(transformations - 1);
  for (int j = 0; j < transformations; ++j) {
    for (int i = 0; i < detail; ++i) {
      float step = j * jToStep;
      c.real = lerp(-1.0, 1.0, step);
      d.imaginary = lerp(-1.0, 1.0, step);
      transformed[j][i] = mobius(a, b, c, d, circle[i]);
    }
  }
}

void draw() {
  surface.setTitle(String.format("%.2f", frameRate));
  background(0xff000000);

  // Manipulate inputs arbitrarily.
  a.real = map(mouseX, 0, width, -1.0, 1.0);
  b.real = map(mouseY, 0, height, -1.0, 1.0);

  float animStep = frameCount * animIncr;
  a.imaginary = cos(animStep);
  b.imaginary = sin(animStep);

  // Draw reference circle.
  stroke(0xff404040);
  beginShape(POLYGON);
  for (int i = 0; i < detail; ++i) {
    vertex(x + circle[i].real * scale, 
      y + circle[i].imaginary * scale);
  }
  endShape(CLOSE);

  // Draw transformations.
  float iToStep = 1.0 / float(detail - 1);
  float jToStep = 1.0 / float(transformations - 1);
  for (int j = 0; j < transformations; ++j) {

    // Manipulate inputs arbitrarily.
    float jStep = j * jToStep;
    c.imaginary = lerp(-1.0, 1.0, jStep);
    d.imaginary = lerp(-1.0, 1.0, jStep);

    stroke(lerpColor(0xffff0000, 0xff00ffff, jStep, HSB));
    beginShape(POLYGON);
    for (int i = 0; i < detail; ++i) {

      // Manipulate inputs arbitrarily.
      float iStep = i * iToStep;
      c.real = lerp(0.0, 2.0, iStep);
      d.real = lerp(-2.0, 0.0, iStep);

      // Mobius transformation, where z is reference circle.
      mobius(a, b, c, d, circle[i], transformed[j][i]);

      vertex(x + transformed[j][i].real * scale, 
        y + transformed[j][i].imaginary * scale);
    }
    
    // Depending on how inputs are manipulated,
    // the resultant shape may or may not be a
    // closed circle.
    // endShape(CLOSE);
    endShape();
  }
}

I’ve assumed that the y-axis represents the imaginary component of the complex number. I imagine you’ll have to move beyond the out-of-the-box primitives (ellipse, rect, etc.). You could use point, then draw points at a high enough density to create a continuous form; use beginShape, vertex and endShape as above; or dig into the pixels array. I find it helpful to keep function inputs and outputs in the range -1 to 1 (or the valid ranges specified by the given formula), then for display purposes translate, rotate and scale them in a separate part of the code.

Adam J. Murray has a gist exploring a Mobius tranformation in Processing as well (he uses a float array with two elements rather than define a Complex class).

Can’t speak to exponential grids or the second image posted, I’m afraid.

1 Like