Simplyomplex

Tutorial

What am I even looking at?

The axes above represent two copies of the complex plane. Both are linked by a function typed in the center text field: the first axes can be drawn on and is used as input, while the output is represented in the second one. But wait, you might say, how can squiggles be used as inputs to a complex function? I thought functions operated on numbers… or whatever complex numbers even are.

The rest of the tutorial is here to explain why and how squiggles in the first axes are being mapped to squiggles in the second axes.

Why would anyone make this?

Complex function visualization is… well… complex. "Regular" functions are easy: just graph the output against the input. 2 dimensions, easily fits in our 3‑dimensional world. Even 2 and 2 functions can be visualized like this.

The complex plane, however, is 2‑dimensional itself. If we wanted to graph output against input, we'd need 4 dimensions, so we have to be more creative with visualization. One well estabilished technique is domain coloring, where you have a 2‑dimensional output image whose pixels are mapped to points in the complex plane. The color of each pixel is then determined by the function value at that pixel's corresponding complex point, with the hue being mapped to the function value's principal argument, and color intensity (or "value") being mapped to its absolute value.

While domain coloring is useful, it has its limitations. For one, people with colorblindness can have some difficulty distinguishing regions in the image. For another, it's difficult to see structures that aren't centered around the origin. Sure, you can see if a path contains all hues and therefore makes a loop around the origin, but try to determine whether a path makes a loop around 1 + i and you're in for a very bad time.

With the limitations of domain coloring, I sought a different way to visualize what a function does to its inputs. And this is when Welch Labs comes in.

How does it work?

This project uses a different approach than domain coloring. I first saw this type of visualization in the following Welch Labs YouTube video.

Pixels in the input plane are mapped to complex numbers, just like in domain coloring, but instead of using fixed colors for every output pixel, we use colors defined in the input plane. So if a red pixel at 1 + i gets mapped to 2 i , the point 2 i in the output plane will be colored red.

This process distorts the input image in a unique way depending on the chosen function. The whole video (really, the whole series) is worth a watch, even if you are already very familiar with complex numbers.

Instead of pixels, however, Simply Complex uses paths as primitves: every squiggle you draw on the first axes is broken into a series of line segments, whose endpoints are mapped according to the given function. After the mapping is complete, the new points in the output axes are connected with the same color as the input squiggle, creating a new morphed path. There's some extra care taken for rapidly changing regions and discontinuities, but those are special case details.

This more abstract approach allows Simply Complex to have essentially infinite resolution. While in the pixels approach, regions with large derivative absolute values will be spread out, leaving uncolored pixels inbetween. With paths, this is not a problem: we simply* calculate the derivative at that point and scale the path width by its absolute value.

*Of course, calculating the derivative of an arbitrary, user-defined function isn't exactly a simple task, but it is possible and I have done so with my SymDiff (for "symbolic differentiation") library.

You can test the path width scaling yourself: try making a path that starts off far from the origin, and goes towards it and eventually reaches it. If the function is set to f ( z ) = z 2 , the output path width will visibly decrease when it gets closer to the origin, eventually disappearing.

Usage

Internally, the function text field is evaluated in a Lua context with some variables predefined. This means you are essentially creating a very very short piece of Lua code, that is run for every point in the paths you draw.

The variables you are allowed to use are:

  • z: a placeholder for whichever point is currently being evaluated;
  • i: the imaginary unit;
  • e: the base of the natural logarithm;
  • pi: the circle constant;
  • tau: the superior circle constant.

The Lua environment is also equipped with several functions. They are:

  • real: real part of its argument;
  • imag: imaginary part of its argument;
  • conj: conjugate of its argument;
  • exp: exponential function, base e;
  • ln: natural logarithm (principal branch);
  • sqrt: square root (principal value);
  • abs: absolute value;
  • arg: argument or phase;
  • sin, cos, tan: circular trigonometric functions;
  • sinh, cosh, tanh: hyperbolic trigonometric functions;