The wonderful London venue IKLECTIK is organising a streaming festival tomorrow:

KLECTIK [off-site] presents OsCiLlaTioNs - Summer Solstice Music Festival.

Sunday 21 June - From sunrise (4.43am) till 00:43am

In 2015 IKLECTIK and The Horse Improvised Music Club organised the first Oscillations edition, a music marathon to celebrate the summer solstice. It was a memorable day: over 18 hours of non-stop music with the participation of more than 60 musicians. Before the virus outbreak we planned to repeat the event but for obvious reasons we will not be able to do so in our venue, therefore, we have decided to do it online!

The purpose of this event is to raise some funds to keep IKLECTIK alive but also to bring us all together against these difficult times.

This event is part of #SaveOurVenues crowdfunding campaign. If you want to donate, please visit https://www.crowdfunder.co.uk/saveiklectik

This event will be live streamed on:

IKLECTIK YouTube channel: https://youtu.be/joHGWoT-pG8

IKLECTIK Twitch channel: https://www.twitch.tv/iklectik

IKLECTIK Facebook page: https://www.facebook.com/IKLECTIK/

With short sets the line-up is quite long! I'm on around 14:20.

]]>Almost a decade ago I wrote about optimizing zoom animations by reusing the center portion of key frame images. In that post I used a fixed scale factor of 2, because I didn't think about generalizing it. This post here is to rectify that oversight.

So, fix the output video image size to \(W \times H\) with a pixel density (for anti-aliasing) of \(\rho^2\). For example, with \(5 \times 5\) supersampling (25 samples per output pixel), \(\rho = 5\).

Now we want to calculate the size of the key frame images \(H_K \times W_K\) and the scale factor \(R\). Suppose we have calculated an inner / deeper key frame in the zoom animation, then the next outer keyframe only needs the outer border calculated, because we can reuse the center. This means only \( H_K W_K \left(1 - \frac{1}{R^2}\right) \) need to be calculated per keyframe. The number of keyframes decreases as \(R\) increases, it turns out to be proportional to \(\frac{1}{\log R}\) for a fixed total animation zoom depth.

Some simple algebra, shows that \(H_K = R \rho H\) and \(W_K = R \rho W\). Putting this all together means we want to minimize the total number of pixels that need calculating, which is proportional to

\[ \frac{R^2 - 1}{\log R} \]

which decreases to a limit of \(2\) as \(R\) decreases to \(1\). But \(R = 1\) is not possible, as this wouldn't zoom at all; as-close-to-1-as-possible means a ring of pixels 1 pixel thick, at which point you are essentially computing an exponential map.

So if an exponential map is the most efficient way to proceed, how much worse is the key frame interpolation approach? Define the efficiency by \(2 / \frac{R^2 - 1}{\log R}\), then the traditional \(R = 2\) has an efficiency of only 46%, \(R = \frac{4}{3}\) 74%, \(R = \frac{8}{7}\) 87%.

These results are pushing me towards adding an exponential map rendering mode to all of my fractal zoom software, because the efficiency savings are significant. Expect it in (at least the command line mode of) the next release of KF which is most likely to be in early September, and if time allows I'll try to make a cross-platform zoom video assembler that can make use of the exponential map output files.

]]>

Music for empty venuesis a charitable music compilation in response to the Covid-19 crisis. Independent music venues across the country are struggling more than ever due to the imposed lockdown, with many listings cancelled and the uncertainty over any future bookings. This compilation aims to support some of the affected venues within the city of London through the means of a fund-raising, with all profits of the release going to three chosen independent London venues selected by the featured artists. These venues are: Iklectik, Hundred Years Gallery and Jazzlive at the Crypt.The compilation features a wide range of forward thinking, electronic based musicians who have graced many of London's (UK) independent music venues over the years. We've all come together in this one moment to support the venues that normally support us.

The compilation will be released on Friday 29th May 2020 at a modest price of £3, however, buyers are encouraged to donate more if they can:

methodicalmovements.bandcamp.com/album/music-for-empty-venues

Methodical Movements is a London (UK) based label supporting experimental, electronic music. Founded by artist Tony James Morton, the label looks to unearth like-minded, forward thinking composers/performers who utilise electronics as a fundamental part of their practice.

My track is a version of **el day de lay** which is
live-coded in the C programming language, based on rhythmically modulated
delays.

Already happening now!

Fringe Arts Bath (FaB) is a test-bed for early career-curators, and those who prefer to operate outside of the gallery-based arts scene. FaB aims to raise the profile of contemporary visual arts in Bath and beyond, providing opportunities for early-career and emerging artists.

...

Co.Lab Sound is an experimental testbed for artists to develop new work and collaborate live. Informed by a long line of experimental music, sound art and performance, these events embrace the processes of improvisation and experimentation to showcase artists expanding the field of sound art and what it means to experience art live.

...

I have 3 works in the online exhibition (Dynamo, disco/designer and Puzzle), and my live slot is scheduled for this Wednesday evening (assuming I can resolve the issues with Zoom...).

]]>Today I released a new version of KF. Kalles Fraktaler 2 + is my fork of Kalle's Fraktaler 2, with many enhancements. KF is a fast deep zoom fractal renderer for Mandelbrot, Burning Ship, and many other formulas. It uses perturbation techniques and series approximation to allow fast low precision deltas per pixel to be used relative to a slow high precision reference orbit per image. The changelog entry is large; over 50 commits since the previous release, affecting 47 files and over 3000 lines. You can download 64bit Windows binaries from the homepage: mathr.co.uk/kf/kf.html (they work in Wine on Linux, and are cross-compiled with MINGW64). I won't replicate the full list of changes, but here are some highlights.

The most visible change is that the old Iterations dialog is gone. In its place are three new dialogs: Formula, Bailout and Information. Some of the controls have moved to the Advanced menu as they shouldn't usually need to be adjusted. The Formula dialog has the fractal type and power and a few other things, the Bailout dialog has things that affect the exit from the inner loop (maximum iteration count, escape radius, and so on).

The main new feature in these dialogs is the ability to control the bailout test with 4 variables: custom escape radius, real and imaginary weights (which can now be any number, including fractions or negative, instead of previously limited to 0 or 1), and norm power. Together these allow for the shape of the iteration bands to be changed in many different ways. The glitch test is now always done with Euclidean norm (unweighted), and reference calculations are simpler because they don't need to calculate their own pixel any more (I prevent the infinite loop of reference being detected as glitch in a different way now).

Colouring-wise, there is the return of the Texture option which had been broken for many years, there is a fourth-root transfer function, and a new phase channel is computed (saved in EXR as T channel in [0..1)). So far it is exposed to colouring only as a "phase strength" setting. It works best with Linear bailout smooth method (Log gives seams as it is independent of escape radius). There is also a new Flat toggle, if you can't stand palette interpolation.

More code is parallelized, as well as significant speedups for Mandelbrot power 3, and faster Newton-zooming for analytic formulas (arbitrary power Mandelbrot and the Redshifter formulas). Upgrading to GMP 6.2 should improve performance especially on AMD Ryzen CPUs.

Lots of bugfixes, including directional DE for NanoMB1+2 and an off-by-one in NanoMB1 that was making colours different vs normal rendering. EXR load and save uses much less memory, and there are new options to select which channels to export for smaller files if you don't need the data.

]]>My video work "Shut Down As A Service" has been published as part of the The Wake UP! Memorial Open Call Corona! Shut down? (submission #43) and as a part of NewMediaFest2020.

The concept is a systemd service unit that shuts down the computer as
soon as it has booted up. I used **qemu** to run a virtual
Debian installation (without KVM acceleration so that the video would be
slow enough), and thanks to constructive feedback I configured grub inside
the emulated Debian to use set the kernel commandline option
**nomodeset** and the terminal to **console**, so
that the **-nographic -serial mon:stdio** arguments to qemu
would make the output go directly to an **xterm** window without any SDL
graphics. The purpose of that was so I could make the text size big enough
to read on smaller web players. Maybe this would have been a good test of **asciinema**
technology but I had annoying issues with unicode, so I ended up using **OBS**
to record lossless video.

xterm -fa "Liberation Mono" -fs 24 -e \ qemu-system-x86_64 -nographic -serial mon:stdio \ -cpu core2duo -m 1G -drive format=raw,file=hda.img

After recording the autonomous session, I exported the lossless video to
a PPM image sequence so I could accurately time where different things started
and ended. I composed the soundtrack in my **clive** environment
for audio in C, the final render is not live-coded but the live reloading was
very helpful while preparing it. Source code for the soundtrack is here,
where most of the magic numbers are frame counts:

code.mathr.co.uk/clive/.../client/go.c

I prepared a file list using **bash**:

#!/bin/bash echo "ffconcat version 1.0" lastfile="" for file in ppm/????.ppm do echo "file ${file}" echo "duration 0.016666666666" lastfile="${file}" done echo "file ${lastfile}"

and encoded them into a video with **ffmpeg**, cropping
to the xterm window and centering in 1920x1080:

ffmpeg -r 60 -i soundtrack.wav -r 60 -i files.list \ -vf 'crop=1764:940:5:135,pad=1920:1080:(ow-iw)/2:(oh-ih)/2' \ -pix_fmt yuv420p -profile:v high -level:v 4.1 -crf:v 20 \ -b:a 384k -movflags +faststart -r 60 \ output.mp4

I'm not sure which **-r 60** made it work, left them all in
just to be sure.

While fine-tuning the boot sequence, I found instructions on how to mount a qemu raw image on my host OS (also Debian) so I could tinker with it after my first attempt at setting up the service made it impossible to log in before it shut down...

sudo losetup /dev/loop0 hda.img sudo kpartx -a /dev/loop0 sudo mount /dev/mapper/loop0p1 hda/

and to unmount

sudo umount hda/ sudo kpartx -d /dev/loop0 sudo losetup -d /dev/loop0

The systemd unit is quite minimal:

[Unit] Description=Corona? Shut down! After=rc-local.service Before=getty.target [Service] ExecStart=/usr/sbin/shutdown now -h

and I installed it by creating a symlink in
`/etc/systemd/system/multi-user.target.wants/`

. I did try
experimenting with different service pre/post requirements, but some of
them made it pause doing nothing obvious for about 10 seconds until the
network was configured, and then messages clobbered the login prompt,
not so nice aesthetically.

*Vertical slices (constant x) have the same number of samples per pixel.
number of samples doubles each slice, from 1 at the left to 16384 at the right.
Horizontal slices (constant y) have density estimation on or off.
The top slice has density estimation on, the bottom slice has density estimation off.
Below the bottom slice are numbers indicating samples per pixel
Quality is low at the left and high at the right.
With density estimation, the left image is blurry but about the same brightness as the right hand side.
Without density estimation, the left image is sparse and noisy, and much darker due to the black background.
Towards the right hand side both methods converge to the same image.*

Fractal flames are generated by applying the chaos game technique to a weighted directed graph iterated function system with non-linear functions. Essentially what this means is, you start from a random point, and repeatedly pick a random transformation from the system and apply it to the point - plotting all the points into an histogram accumulation buffer, which ends up as a high dynamic range image that is compressed for display using a saturation-preserving logarithm technique.

The main problem with the chaos game is that you need to plot a lot of points, otherwise the image is noisy. So to get acceptable images with fewer numbers of points, an adaptive blur algorithm is used to smooth out the sparse areas, keeping the dense areas sharp:

Adaptive filtering for progressive monte carlo image rendering

by Frank Suykens , Yves D. Willems

The 8-th International Conference in Central Europe on Computer Graphics, Visualization and Interactive Digital Media 2000 (WSCG' 2000), February 2000. Held in Plzen, Czech Republic

Abstract

Image filtering is often applied as a post-process to Monte Carlo generated pictures, in order to reduce noise. In this paper we present an algorithm based on density estimation techniques that applies an energy preserving adaptive kernel filter to individual samples during image rendering. The used kernel widths diminish as the number of samples goes up, ensuring a reasonable noise versus bias trade-off at any time. This results in a progressive algorithm, that still converges asymptotically to a correct solution. Results show that general noise as well as spike noise can effectively be reduced. Many interesting extensions are possible, making this a very promising technique for Monte Carlo image synthesis.

The problem with this density estimation algorithm as presented in the paper is that it is very expensive: asymptotically it's about \(O(d^4)\) where \(d\) is the side length of the image in pixels. It's totally infeasible for all but the tiniest of images. But I did some thinking and figured out how to make it \(O(d^2)\), which at a constant cost per pixel is the best it is possible to do.

So, starting with the high dynamic range accumulation buffer, I
compute a histogram of the alpha channel with log-spaced buckets. As
I am using ulong (uint64_t) for each channel, a cheap way to do this
is the `clz()`

function, which counts leading zeros. The
result will be between 0 and 64 inclusive. Now we have a count of how
many pixels are in each of the 65 buckets.

For each non-empty bucket (that doesn't correspond to 0), threshold the HDR accumulation buffer to keep only the pixels that fall within the bucket, setting all the rest to 0. Then blur this thresholded image with radius inversely proportional to \(\sqrt{\text{bucket value}}\) (so low density is blurred a lot, highest density is not blurred at all). And then sum all the blurred copies into a new high dynamic range image, before doing the log scaling, auto-white-balance, gamma correction, etc as normal.

The key to making it fast is making the blur fast. The first observation is that Gaussian blur is separable: you can do a 2D blur by blurring first horizontally and then vertically (or vice versa), independently. The second observation is that repeated box filters tend to Gaussian via the central limit theorem. And the third important observation is that wide box filters can be computed just as cheaply as narrow box filters by using a recursive formulation:

for (uint x = 0; x < width; ++x) { acc -= src[x - kernel_width]; acc += src[x]; dst[x] = acc / kernel_width; }

There is some subtlety around initializing the boundary conditions (whether to clamp or wrap or do something else), and to avoid shifting the image I alternate between left-to-right and right-to-left passes. A total of 8 passes (4 in each dimension, of which 2 in each direction) does the trick. There are at most 64 copies to blur, but in practice the number of non-empty buckets is much less (around 12-16 in my tests).

As a bonus, here's the page from my notebook where I first developed this fast technique:

Thanks to lycium for pointing out to me the second and third observations about box filters.

**EDIT:** I benchmarked it a bit more. At large image
sizes (~8k^2) with extra band density (needed to avoid posterization
artifacts from filter size quantization), it's about 40x slower (2mins)
than Fractorium (5secs). If the sample/pixel count not ridiculously
small they are indistinguishable (maybe mine is a very tiny bit smoother
in less dense areas); for ridiculously small sample counts mine is very
blurry and Fractorium's is very noisy, but 2mins iteration and 5secs
filter definitely beats 5secs iteration and 2mins filter in terms of
visual quality any way you do it. So I guess it's "nice idea in theory,
but in practice...".

Bytebeat is a genre of music using small programs to generate digital audio data. Often the resolution is low (8bit) as well as the sample rate (8kHz), so comparisons can be made to chiptune aesthetics, though the method of sound generation is different (CPU controlling a DAC vs specialized sound chips). The thread that began the modern bytebeat phenomenon is already over 8 years old:

Algorithmic symphonies from one line of code -- how and why?

Lately, there has been a lot of experimentation with very short programs that synthesize something that sounds like music. I now want to share some information and thoughts about these experiments.

Most computer languages use infix operators with varying precedence levels (so * for multiplication binds more tightly than + for addition). Brackets are used to override the precedence, so for example "a * b + c" means "(a * b) + c" and you need brackets if you want "a * (b + c)". When coding bytebeat, one often uses integer bitwise operators like & | ^, as well as the more usual arithmetic. I find it impossible to keep track of all the precedence levels in my head, so I have to add brackets everywhere, and then I have to match up brackets with each other, which is a non-local editing process, which is slow when live-coding.

Enter postfix notation: instead of "a * b + c", write "a b * c +", and for "a * (b + c)" write "a b c + *" or "b c + a swap *" or (using the commutativity of "*") "b c + a *". No brackets, but there is still some counting. I wrote a postfix language aimed at bytebeat, inspired by Forth though it isn't a Forth. I called it Barry, because I already have projects named Clive, Raymond, Trudy, Harry. The file extensions is .baz, the interpreter library called Barrington, the compiler is (boringly) named bazc (actually there are two variants, bazc-simple does forward tracking of types from arguments to outputs via stack and locals, bazc-unified does multi-way unification which is more powerful but my implementation is much too slow to be useful). There's a helper program called Barrence which I needed to topsort the dictionary for bazc when experimenting with the collaborative editor Troop.

I wrote a compiler (which compiles to C) because Barrington the interpreter was much too slow. But then I had the idea of porting Barry to run in a web browser, so I made improvements to Barrington to make it much faster. It now has a DSP chain, similar to Pd, where the main loop just pops function pointers and calls them, with all the type checking done once at DSP chain compilation time. It also uses block-based processing, so the overhead of dispatch is much smaller. There is no state in the language (nor branching, loops, recursion) - the only thing that changes is the input, which is a time counter and the audio channel number. Maybe this will change but I'll need to find a way to make it safe and guaranteed crash-free - I already have protection against integer division by 0 etc.

You can try Barry in the browser via the Barry home page, which also describes how to use it with Clive, my thing for live-coding audio in the C programming language:

mathr.co.uk/barry

I plan to add document permalinks (so you can save your work without having to copy/paste into a text editor) in the next update, not sure when that will be though. It will probably be via base64 encoding after the # anchor, so that it doesn't get sent to the server (and also meaning that Barry web app cab be cached by the browser).

There were a few things needed to get the web version working nicely: Emscripten/SDL2 seems to do audio on the main thread, but chain recompilation can take too long and interrupt it - luckily the --proxy-to-worker link time flag seems to solve this. And allowing memory growth was another huge slowdown, so I set total memory to 64MB, which should be enough: typically the audio block size is 4096 samples each of which take 8 bytes, and 1 block is allocated per operation (no recycling yet, I couldn't get it working safely but it's on my list to retry soon). And I wrote a new shell HTML/JS which is simpler than the default Emscripten one, with additional features like an audio context resume button needed to make sound at all in some browsers.

]]>Deep zoom is a subgenre of fractal video art. Shapes in the Mandelbrot set (and other fractals) stack up in nested period-doubling crises as you pass by mini-sets and the embedded Julia sets between them.

Mandelbrot set deep zooms are computationally expensive, requiring lots of high precision numerical calculations. Computational cost has a corresponding ecological cost. The climate is the real crisis.

This is not a Mandelbrot set deep zoom, but it might resemble one at first glance with its nested period-doubling crises. But instead it is designed to have a similar appearance with much cheaper equations.

Rendering of v1 (duration one hour) at 1920x1080p60@50Mbps took a little over four hours wall-clock time. Two-pass video encoding to 960x540p60@4Mbps took a little over half an hour wall-clock time. I estimate around 1kWh of electricity was used.

Summary of the key parts of the algorithm:

for (l = L; l >= 0; --l) t = -log2(1 - t) n += floor(t) t -= floor(t) t = (1 - exp2(-t)) * 2 // store n and t for level l

Drone soundtrack based on the same code used for the graphics.

]]>In an appendix to the paper from which I implemented the slow mating algorithm in my previous post, there is a brief description of another algorithm:

The Thurston Algorithm for quadratic matings

Initialization A.2 (Spider algorithm with a path)Suppose \(\theta = \theta_1 \in \mathbb{Q} \backslash \mathbb{Z}\) has prepreperiod \(k\) and period \(p\). Define \((x_1(t), \ldots, x_{k+p}(t))\) for \(0 \le t \le 1\) as

\[ x_1(t) = t e^{i 2 \pi \theta_1} \\ x_p(t) = (1 - t) e^{i 2 \pi \theta_p}, \text{ if } k = 0 \\ x_j(t) = e^{i 2 \pi \theta_j}, \text{ otherwise.} \]Pull this path back continuously with \(x_i(t + 1) = \pm \sqrt{x_{i+1}(t)-x_1(t)}\). Then it converges to the marked points of \(f_c\) with appropriate collisions.

In short, given a rational \(\theta\) measured in turns, this provides a way to calculate \(c\) in the Mandelbrot set that has corresponding dynamics. Here \(\theta_j = 2^{j - 1} \theta \mod 1\), and the desired \(c = x_1(\infty)\).

This week I implemented it in my mandelbrot-numerics library, in the hope that it might be faster than my previous method of tracing external rays. Alas, it wasn't to be: both algorithms are \(O(n^2)\) when ignoring the way cost varies with numerical precision, and the spider path algorithm has higher constant factors and requires \(O(n)\) space vs ray tracing \(O(1)\) space. This meant spider path was about 6x slower than ray tracing when using a single-threaded implementation, in one test at period 469, and I imagine it would be slower still at higher periods and precisions.

This isn't entirely surprising, spider path does \(s n\) complex square roots to extend the paths by \(t \to t + 1\), while ray trace does \(s t\) arithmetical operations to extend the ray from depth \(t \to t + 1\). The \(O(n^2)\) comes from \(t\) empirically needing to be about \(2 n\) to be close enough to switch to the faster Newton's root finding method.

Moreover spider path needs very high precision all the way through, the initial points on the unit circle need at least \(n\) bits (I used about \(2 n\) to be sure) to resolve the small differences in external angles, even though the final root can usually be distinguished from other roots of the same period using much less precision. In fact I measured spider path time to be around \(O(n^{2.9})\), presumably because of the precision. Ray tracing was very close to \(O(n^2)\).

Ray tracing has a natural stopping condition: when the ray enters the atom domain with period \(p\), Newton's method is very likely to converge to the nucleus at its center. I imagine something similar will apply to preperiodic Misiurewicz domains, but I have not checked yet. I tried it with spider path but in one instance I got a false positive and ended up at a different minibrot to the one I wanted.

The only possible advantages that remain for the spider path algorithm is that it can be parallelized more effectively than ray tracing, and that the numbers are all in the range \([-2,2]\) which means fixed point could be used. Perhaps a GPU implementation of spider path would be competitive with ray tracing on an elapsed wall-clock time metric, though it would probably still lose on power consumption.

I plotted a couple of graphs of the spider paths, the path points end up log-spiraling around their final resting places. I think this means it converges linearly. Ray tracing is also linear when you are far from the landing point (before the period-doubling cascade starts in earnest). Newton's method converges quadratically, which means the number of accurate digits doubles each time, but you need to start from somewhere accurate enough.

]]>I recently came across Arnaud Chéritat's polynomial mating movies and just had to try to recreate them.

If \(p\) and \(q\) are in the Mandelbrot set, they have connected Julia sets for the quadratic polynomial functions \(z^2+p\) and \(z^2+q\). If they are not in conjugate limbs (a limb is everything beyond the main period 1 cardioid attached at the root of a given immediate child bulb, conjugation here is reflection in the real axis, the 1/2 limb is self-conjugate) then the Julia sets can be mated: glue the Julia sets together respecting external angles so that the result fills the complex plane (which is conveniently represented as the Riemann sphere). It turns out that this mating is related to the Julia set of a rational function of the form \(\frac{z^2+a}{z^2+b}\).

One algorithm to compute \(a\) and \(b\) is called "slow mating". Wolf Jung has a pre-print which explains how to do it in chapter 5: The Thurston Algorithm for quadratic matings.

My first attempts just used Wolf Jung's code and later my own code, to compute the rational function and visualize it in Fragmentarium (FragM fork). This only worked for displaying the final limit set, while Chéritat's videos had intermediate forms. I found a paper which had this to say about it:

On The Notions of Mating

Carsten Lunde Petersen & Daniel Meyer

5.4. Cheritat moviesIt is easy to see that \(R_\lambda\) converges uniformly to the monomial \(z^d\) as \(\lambda \to \infty\). Cheritat has used this to visualize the path of Milnor intermediate matings \(R_\lambda\), \(\lambda \in ]1,\infty[\) of quadratic polynomials through films. Cheritat starts from \(\lambda\) very large so that \(K_w^\lambda\) and \(K_b^\lambda\) are essentially just two down scaled copies of \(K_w\) and \(K_b\), the first near \(0\), the second near \(\infty\). From the chosen normalization and the position of the critical values in \(K_w^\lambda \cup K_b^\lambda\) he computes \(R_\sqrt{\lambda}\). From this \(K_w^\sqrt{\lambda} \cup K_b^\sqrt{\lambda}\) can be computed by pull back of \(K_w^\lambda \cup K_b^\lambda\) under \(R_\sqrt{\lambda}\). Essentially applying this procedure iteratively one obtains a sequence of rational maps \(R_{\lambda_n}\) and sets \(K_w^{\lambda_n} \cup K_b^{\lambda_n}\), where \(\lambda_n \to 1+\) and \(\lambda_n^2 = \lambda_{n-1}\). For more details see the paper by Cheritat in this volume.

What seems to be the paper referred to contains this comment:

Tan Lei and Shishikura’s example of non-mateable degree 3 polynomials without a Levy cycle

Arnaud Chéritat

Figure 4The Riemann surface \(S_R\) conformally mapped to the Euclidean sphere, painted with the drawings of Figure 2. The method for producing such a picture is interesting and will be explained in a forthcoming article; it does not work by computing the conformal map, but instead by pulling-back Julia sets by a series of rational maps. It has connections with Thurston's algorithm.

I could not find that "forthcoming" article despite the volume having been published in 2012 following the 2011 workshop, so I emailed Arnaud Chéritat and got a reply to the effect that it had been cancelled by the author.

My first attempts at coding the slow mating algorithm worked by pulling back the critical orbits as described in Wolf Jung's preprint. The curves look something like this:

A little magic formula for finding the parameters \((a, b)\) for the function \(\frac{z^2+a}{z^2+b}\):

\[a = \frac{C(D-1)}{D^3(1-C)}\] \[b = \frac{D-1}{D^2(1-C)}\]

where \((C,D)\) are the pulled back \(1\)th iterates. This was reverse-engineered from Wolf Jung's code, which worked with separate real and imaginary components, with no comments and heavy reuse of the same variable names. I'm not sure if it is correct but it seems to give useable results when plugged into FragM for visualization:

I struggled to implement the intermediate images at first: I tried pulling back from the coordinates of a filled in Julia set but that needed huge amounts of memory and the resolution was very poor:

Eventually I figured out that I could invert each pullback function into something of the form \(\frac{az^2+b}{cz^2+d}\) and push forward from pixel coordinates to colour according to which hemisphere it reached:

I struggled further, until I found the two bugs that were almost cancelling each other out. The coordinates in each respective hemisphere can be rescaled, and thereafter regular iterations of \(z^2+c\) until escape or maximum iterations could be used to colour the filled in Julia sets expanding within each hemisphere:

After that it was quite simple to bolt on dual-complex-numbers for automatic differentiation, to compute the derivatives for distance estimation to make the filaments of some Julia sets visible:

I also added an adaptive super-sampling scheme: if the standard deviation of the current per-pixel sample population divided by the number of samples is less than a threshold, I assume that the next sample will make negligible changes to the appearance, and so I stop. This speeds up interior regions (which need to be computed to the maximum iteration count) because the standard deviation will be 0 and it will stop after only the minimum sample count. I also have a maximum sample count to avoid taking excessive amounts of time. I do blending of samples in linear colour space, with sRGB conversion only for the final output.

Get the code:

git clone https://code.mathr.co.uk/mating.git

Currently about 600 lines of C99 with GNU getopt for argument parsing, but I may port the image generation part to OpenCL because my GPU is about 8x faster than my CPU for some double-precision numerical algorithms, which will help when rendering animations.

]]>Over xmas I played around with LaTeX to see about making a book of all my code. The amount of code surprised me, around 7000 pages, so I don't think I'll print it after all (print on demand would be around £1.50/volume + 2p/page for paperback, significantly more per volume hardback). It was suggested to me that microfilm would be best for long term archival, so maybe I'll think about that - it costs around 2p/page for 16+ rolls from digital, though I don't know how many pages a roll contains.

The main issue I faced when converting the code was that the listings package does not support UTF-8 properly. Yaxu posted a clean solution on stack exchange, but it involves listing each extended character with its LaTeX replacement, and Unicode has many code points. I have not tried this yet, instead using iconv to transliterate to Latin1 and hoping for the best (I also needed some mild sed to transliterate some Latin1 characters that are only valid in maths mode).

My experiments are in my code hosting, with configuration files for most of code.mathr.co.uk and some third-party software (mainly numerical code that I want to read):

git clone https://code.mathr.co.uk/source-codes.git

Future work may evolve into adding hyperlinks within each PDF, so that you can click on the usage of a symbol to jump to its definition; I'd probably need to use something other than Bash to implement that, maybe Haskell has libraries for enough languages, or maybe Python (though I don't like it).

I uploaded some snapshots of my repositories.

]]>