meshwalk-3.0 is a 360 animation of animated Voronoi cells on the surface of a sphere. Rapid acceleration is highlighted visually and sonified spatially. The movement of the dynamic mesh in this version is partly based on the idea of potential wells for crystal formation, and is realized efficiently by updating a collection of nearest neighbours to each node using the neighbours of neighbours (and hoping that it doesn't move so fast that the algorithm would break).

At each time step, after updating the positions of each node (which depends on attraction and repulsion between neighbouring cells), the neighbour list of each cell is updating by choosing the 8 nearest cells among the neighbours and neighbours of neighbours. As the nodes are moving, a neighbour of neighbour might now be nearer than the previous neighbours, so the mesh is dynamically changing. The number 8 can be changed at compile time, too high and it slows down, too low and it breaks (the mesh splits apart and can overlap itself without rejoining).

This mesh algorithm is O(N B^2), where N is the number of nodes and B is the number of neighbours to keep track of. As B is typically a small constant, this reduces to O(N). A naive algorithm is O(N^2) considering all nodes for each node, which I do do in the one-time initialization of the neighbour data. A spatial data structure could probably reduce this cost to O(N log N), but for a few hundred nodes on a sphere it isn't too slow. The Vornoi cell rendering is O(N W H), my GPU is fast so it isn't an issue.

]]>In yesterday's post I showed how dividing by unwanted roots leads to better stability when finding periodic nuclei \(c\) that satisfy \(f_c^p(0) = 0\) where \(f_c(z) = z^2 + c\). Today I'll show how two techniques can bring this gain to finding periodic cycles \(z_0\) that satisfy \(f_c^p(z_0) = z_0\) for a given \(c\).

The first attempt is just to do the Newton's iterations without any wrong root division, unsurprisingly it isn't very successful. The second attempt divides by wrong period roots, and is a bit better. The third algorithm is much more involved, thus slower, but is much more stable (in terms of larger Newton basins around the desired roots).

Here are some images, each row corresponds to an algorithm as introduced. The colouring is based on lifted domain colouring of the derivative of the limit cycle: \(\left|\frac{\partial}{\partial z}f_c^p(z_0)\right| \le 1\) in the interior of hyperbolic components, and acts as conformal interior coordinates which do extend a bit into the exterior.

The third algorithm works by first finding a \(c_0\) that is a periodic nucleus, then we know that a good \(z_0\) for this \(c_0\) is simply \(0\). Now move \(c_0\) a little bit in the direction of the real \(c\) that we wish to calculate, and use Newton's method with the previous \(z_0\) as the initial guess to find a good \(z_0\) for the moved \(c_0\). Repeat until \(c_0 \to c\) and hopefully the resulting \(z_0\) will be as hoped for, in the periodic cycle for \(c\).

Source code for Fragmentarium: 2018-11-18_newtons_method_for_periodic_cycles.frag.

]]>Previously on mathr: Newton's method for Misiurewicz points (2015). This week I applied the same "divide by undesired roots" technique to the periodic nucleus Newton iterations. I implemented it GLSL in Fragmentarium, which has a Complex.frag with dual numbers for automatic differentiation (this part of the frag is mostly my work, but I largely copy/pasted from C99 standard library manual pages for the transcendental functions, Wikipedia for basic properties of differentiation like product rule, quotient rule, chain rule...).

Here's the improved Newton's method, with the newly added lines in bold:

vec2 nucleus(vec2 c0, int period, int steps) { vec2 c = c0; for (int j = 0; j < steps; ++j) { vec4 G = cConst(1.0); vec4 z = cConst(0.0); for (int l = 1; l <= period; ++l) { z = cSqr(z) + cVar(c);if (l < period && period % l == 0) G = cMul(z, G);} G = cDiv(z, G); c -= cDiv(G.xy, G.zw); } return c; }

And some results, top half of the image is without the added lines, bottom half of the image is with the added line, from left to right the target periods are 2, 3, 4, 9, 12:

You can download the FragM source code for the images in this article: 2018-11-17_newtons_method_for_periodic_points.frag.

]]>I'm playing at ArtFutura in Stour Space next month! Screenings, talks, performances.

]]>## ArtFutura 2018 London

Stour Space

7 Roach Road, Hackney Wick, London E3 2PA

+44 (0) 2089857827## Thursday 22nd November 2018

- 19:30
- Doors
- 20:00
- Premiere (1h) + Behind the Scenes (30min)
- 21:30
- Artist talk - Paul Friedlander (1h)
- 22:30
- Live performance - Claude Heiland-Allen
## Friday 23rd November 2018

- 19:30
- Doors
- 20:00
- Estela Oliva presents CLON (30 min)
- 20:30
- 3D Futura show (1h)
- 21:30
- Futura graphics (1h)
- 22:30
- Live AV performance - Mowgli and the slate pipe banjo draggers
## Saturday 24rd November 2018

- 19:30
- Doors
- 20:00
- Artworks (40min)
- 21:00
- Schools (1h)
- 22:00
- Live AV performance - Christian Duka & Marco Maldarella

While evaluating whether to dive in and get a Bela single board computer for low latency processing of external inputs, that I would want to livecode in the C programming language using my clive system, I found that my initial attempts at cross-compiling into a network-shared folder were doomed, because my host system was running Debian Buster (current Testing, next Stable) and my device system (a Raspberry Pi Model 3 B) was running Raspbian Jessie (current OldStable). Jessie has an ancient glibc, the basic libraries that let C programs and libraries work, and my cross-compiled code needed a newer version.

So I installed Debian Buster arm64 from
unofficial unsupported sources.
After upgrading the system to my liking, I tried the audio through HDMI.
The `aplay` command could make some noise, but it was using a
Pulseaudio backend. Try as I might I couldn't get a raw ALSA backend
to make any sound, especially not with JACK (which output errors about
not supported hardware sample types). So I resorted to radical means.
I set up a JACK server using the dummy backend, and hooked Pulseaudio
to it with `pulseaudio-module-jack`. Then adding a loopback
Pulseaudio device to route the JACK sources/sinks to the hardware:

$ /usr/bin/jackd -ddummy & $ tail /etc/pulse/default.pa load-module module-jack-source load-module module-jack-sink set-default-sink jack_out set-default-source jack_in load-module module-loopback source=jack_in sink=alsa_output.platform-3f902000.hdmi.iec958-stereo $ mplayer -ao jack somefile.ogg -loop 0 & $ jack_connect "MPlayer [15406]:out_0" "PulseAudio JACK Source:front-left" $ jack_connect "MPlayer [15406]:out_1" "PulseAudio JACK Source:front-right"

Note: this is significantly different to the usual software -> Pulse -> JACK -> ALSA -> ears route, it's more like software -> JACK -> Pulse -> ALSA -> ears, though I don't really know what happens beyond Pulse in this setup... All I know is I hear sounds from JACK on the HDMI output, which is all that matters.

Now the last thing is figuring out how to disable the screensaver timeout on the lightdm greeter login prompt, when that kicks in all sound goes off. I can disable the screensaver if I login, but an automatically logged in session is a bit annoying. There is a greeter screensaver timeout variable, but that applies only to lock screen, not login screen. I want to make sounds through HDMI from a remote ssh session.

And another shiny thing would be automatically considering the PulseAudio JACK ports as physical and the JACK dummy system: ports as not, so that automatic connections work as expected (I hear nothing until I manually connect the ports as above).

]]>Start with the iteration formula for the Burning Ship escape time fractal:

\[ X := X^2 - Y^2 + A \quad\quad Y := \left|2 X Y\right| + B \]

Perturb this iteration, replacing \(X\) by \(X+x\) (etc) where \(x\) is a small deviation:

\[ x := (2 X + x) x - (2 Y + y) y + a \quad\quad y := 2 \operatorname{diffabs}(X Y, X y + x Y + x y) + b \]

Here \(\operatorname{diffabs}(c,d)\) is laser blaster's formula for evaluating \(|c + d| - |c|\) without catastrophic cancellation; see my post about perturbation algebra for more details.

Now replace \(x\) and \(y\) by bivariate power series in \(a\) and \(b\):

\[ x = \sum_{i,j} s_{i,j} a^i b^j \quad\quad y = \sum_{i,j} t_{i,j} a^i b^j \]

To implement this practically (without lazy evaluation) I pick an order \(o\) and limit the sum to \(i + j \le o\). Substituting these series into the perturbation iterations, and collecting terms, gives iteration formulae for the series coefficients \(s_{i,j}\) and \(t_{i,j}\):

\[ s_{i,j} := 2 X s_{i,j} - 2 Y t_{i,j} + \sum_{k=0,l=0}^{k=i,l=j} \left( s_{k,l} s_{i-k,j-l} - t_{k,l} t_{i-k,j-l} \right) + \mathbb{1}_{i=1,j=0} \]

The formula for \(t\) requires knowing which branch of the \(\operatorname{diffabs}(X Y, 0)\) was taken, which turns out has a nice reduction to \(\operatorname{sgn}(XY)\):

\[ t_{i,j} := 2 \operatorname{sgn}(X Y) \left( X t_{i,j} + s_{i,j} Y + \sum_{k=0,l=0}^{k=i,l=j} \left( s_{k,l} t_{i-k,j-l} \right) \right) + \mathbb{1}_{i=0,j=1} \]

\(\mathbb{1}_F\) is the indicator function, \(1\) when \(F\) is true, \(0\) otherwise. For distance estimation, the series of the derivatives are just the derivatives of the series, which can be computed very easily.

The series is only valid in a region that doesn't intersect an axis, at which point the next iteration will fold the region in a way that a series can't represent. Moreover, the series loses accuracy the further from the reference point, so there needs to be a way to check that the series is ok to use. One approach is to iterate points on the boundary of the region using perturbation, and compare the relative error against the same points calculated with the series. Only if all these probe points are accurate, is it safe to try the next iteration.

This usually means that the series will skip at most \(P\) iterations near a central miniship reference of period \(P\). But one can do better, by subdividing the region into parts that are folded in different ways. The parts that are folded the same way as the reference can continue with series approximation, with probe points at the boundary of each part, with the remainder switching to regular perturbation initialized by the series.

It may even be possible to use knighty's techniques like Taylor shift, which is a way to rebase a series to a new reference point (for example, one in the "other side" of the fold) to split the region into two or more separate parts each with their own series approximation. The Horner shift algorithm is not too complicated, and I think it can be extended to bivariate series by shifting along each variable in succession:

// Horner shift code from FLINT2 for (i = n - 2; i >= 0; i--) for (j = i; j < n - 1; j++) poly[j] += poly[j + 1] * c

Untested Haskell idea for bivariate shift:

shift :: Num v => v -> Map Int v -> Map Int v shift = undefined -- left as an exercise shift2 :: Num v => v -> v -> Map (Int, Int) v -> Map (Int, Int) v shift2 x y = twiddle . pull . fmap (shift x) . push . twiddle . pull . fmap (shift y) . push push :: (Ord a, Ord b) => Map (a, b) v -> Map a (Map b v) push m = M.fromListWith M.union [ (i, M.singleton j e) | ((i,j), e) <- M.assocs m ] pull :: (Ord a, Ord b) => Map a (Map b v) -> Map (a, b) v pull m = M.fromList [((i,j),e) | (i, mj) <- M.assocs m, (j, e) <- M.assocs mj ] twiddle :: (Ord a, Ord b) => Map (a, b) v -> Map (b, a) v twiddle = M.mapKeys (\(i,j) -> (j,i))

Exciting developments! I hope to release a new version of Kalles Fraktaler 2 + containing at least some of these algorithms soon...

]]>The current work-in-progress version of GraphGrow has three components that communicate via OSC over network. The visuals are rendered in OpenGL using texture array feedback, this process is graphgrow-video. The transformations are controlled by graphgrow-iface, with the familiar nodes and links graphical user interface. The interface runs on Linux using GLFW, and I'm working on an Android port for my tablet using GLFM. The component I'll be talking about in this post is the graphgrow-audio engine, which makes sounds using an audio feedback delay network with the same topology as the visual feedback network. Specifically, I'll be writing up my notes on what I did to make it around 2x as CPU efficient, while still making nice sounds.

First up, I tried gprof, but after following the instructions I only got an empty profile. My guess is that it doesn't like JACK doing the audio processing in a separate realtime thread. So I switched to perf:

perf record ./graphgrow-audio perf report

Here's the first few lines of the first report, consider it a baseline:

Overhead Command Shared Object Symbol 34.41% graphgrow-audio graphgrow-audio [.] graphgrow::operator() 18.11% graphgrow-audio libm-2.24.so [.] expm1f 14.27% graphgrow-audio graphgrow-audio [.] audiocb 8.69% graphgrow-audio libm-2.24.so [.] sincos 8.34% graphgrow-audio libm-2.24.so [.] __logf_finite 4.47% graphgrow-audio libm-2.24.so [.] tanhf

That was after I already made some algorithmic improvements: I had 32 delay lines, all of the same length, with 64 delay line readers in two groups of 32, each group reading at the same offset every sample. This meant there was a lot of duplicated work calculating the delay line interpolation coefficients. I factored out the computation of the delay coefficients into another struct, which could be calculated 1x per sample instead of 32x per sample. Then the delay readers are passed the coefficients, instead of computing them themselves.

Looking at what to optimize, the calls to expm1f() seem to be a big target. Looking through the code I saw that I had 32 dynamic range compressors, each doing RMS to dB (and back) conversions every sample, which means a lot of log and exp. My compressor had a ratio of 1/8, so I replaced the gain logic by a version that worked in RMS with 3x sqrt instead of 1x log + 1x exp per sample:

index a6ba512..588d098 100644 --- a/graphgrow3/audio/graphgrow-audio.cc +++ b/graphgrow3/audio/graphgrow-audio.cc @@ -549,18 +549,25 @@ struct compress sample factor; hip hi; lop lo1, lo2; + sample thresrms; compress(sample db) : threshold(db) , factor(0.25f / dbtorms((100.0f - db) * 0.125f + db)) , hi(5.0f), lo1(10.0f), lo2(15.0f) + , thresrms(dbtorms(threshold)) { }; signal operator()(const signal &audio) { signal rms = lo2(0.01f + sqrt(lo1(sqr(hi(audio))))); +#if 0 signal db = rmstodb(rms); db = db > threshold ? threshold + (db - threshold) * 0.125f : threshold; signal gain = factor * dbtorms(db); +#else + signal rms2 = rms > thresrms ? thresrms * root8(rms / thresrms) : thresrms; + signal gain = factor * rms2; +#endif return tanh(audio / rms * gain); }; };

This seemed to work, the new perf output was:

Overhead Command Shared Object Symbol 38.89% graphgrow-audio graphgrow-audio [.] graphgrow::operator() 22.11% graphgrow-audio libm-2.24.so [.] expm1f 10.78% graphgrow-audio libm-2.24.so [.] sincos 5.76% graphgrow-audio libm-2.24.so [.] tanhf

The numbers are higher, but this is actually an improvement, because if graphgrow::operator() goes from 34% to 39%, everything else has gone from 66% to 61%, and I didn't touch graphgrow::operator(). Now, there are still some large amounts of expm1f(), but none of my code calls that, so I made a guess: perhaps tanhf() calls expm1f() internally? My compressor used tanh() for soft-clipping, so I tried simply removing the tanh() call and seeing if the audio would explode or not. In my short test, the audio was stable, and CPU usage was greatly reduced:

Overhead Command Shared Object Symbol 60.53% graphgrow-audio graphgrow-audio [.] graphgrow::operator() 17.62% graphgrow-audio libm-2.24.so [.] sincos 11.51% graphgrow-audio graphgrow-audio [.] audiocb

The next big target is that sincos() using 18% of the CPU. The lack of 'f' suffix tells me this is being computed in double precision, and the only place in the code that was doing double precision maths was the resonant filter biquad implementation. The calculation of the coefficients used sin() and cos(), at double precision, so I swapped them out for single precision polynomial approximations (9th order, I blogged about them before). The approximation is roughly accurate (only a bit or two out) for float (24bits) which should be enough: it's only to control the angle of the poles, and a few cents (or more, I didn't check) error isn't so much to worry about in my context. Another big speed improvement:

Overhead Command Shared Object Symbol 85.48% graphgrow-audio graphgrow-audio [.] graphgrow::operator() 11.45% graphgrow-audio graphgrow-audio [.] audiocb 1.22% graphgrow-audio libc-2.24.so [.] __isinff 0.64% graphgrow-audio libc-2.24.so [.] __isnanf 0.41% graphgrow-audio graphgrow-audio [.] graphgrow::graphgrow

perf has a mode that annotates the assembly and source with hot instructions, looking at that let me see that the resonator was using double precision sqrt() when calculating the gain when single precision sqrtf() would be enough:

Overhead Command Shared Object Symbol 88.70% graphgrow-audio graphgrow-audio [.] graphgrow::operator() 8.49% graphgrow-audio graphgrow-audio [.] audiocb 1.24% graphgrow-audio libc-2.24.so [.] __isinff 0.65% graphgrow-audio libc-2.24.so [.] __isnanf

Replacing costly double precision calculations with cheaper single precision calculations was fun, so I thought about how to refactor the resonator coefficient calculations some more. One part that definitely needed high precision was the calculation of 'r = 1 - t' with t near 0. But I saw some other code was effectively calculating '1 - r', which I could replace with 't', and make it single precision. Again, some code was doing '1 - c * c' with c the cosine of a value near 0 (so 'c' is near 1 and there is catastrophic cancellation), using basic trigonometry this can be replaced by 's * s' with s the sine of the value. However, I kept the final recursive filter in double precision, because I had bad experiences with single precision recursive filters in Pure-data (vcf~ had strongly frequency-dependent ring time, porting to double precision fixed it).

Overhead Command Shared Object Symbol 87.86% graphgrow-audio graphgrow-audio [.] graphgrow::operator() 9.18% graphgrow-audio graphgrow-audio [.] audiocb 1.37% graphgrow-audio libc-2.24.so [.] __isinff 0.68% graphgrow-audio libc-2.24.so [.] __isnanf

The audiocb reponds to OSC from the user interface process, it took so much time because it was busy-looping waiting for the JACK processing to be idle, which was rare because I still hadn't got the CPU load down to something that could run XRUN-free in realtime at this point. I made it stop that, at the cost of increasing the likelihood of the race condition when storing the data from OSC:

Overhead Command Shared Object Symbol 96.87% graphgrow-audio graphgrow-audio [.] graphgrow::operator() 1.49% graphgrow-audio libc-2.24.so [.] __isinff 0.80% graphgrow-audio libc-2.24.so [.] __isnanf

Still not running in realtime, I took drastic action, to compute the resonator filter coefficients only every 64 samples instead of every 1 sample, and linearly interpolate the 3 values (1x float for gain, 2x double for feedback coefficients). This is not a really nice way to do it from a theoretical standpoint, but it's way more efficient. I also check for NaN or Infinity only at the end of each block of 64 samples (if that happens I replace the whole block with zeroes/silence), which is also a bit of a hack - exploding filters sound bad whatever you do to mitigate it, but I haven't managed to make it explode very often.

Success: now it was using 60% of the CPU, comfortably running in real time with no XRUNs. So I added in a (not very accurate, but C2 continuous) rational approximation of tanh() to the compressor that I found on musicdsp (via Pd-list):

signal tanh(const signal &x) { signal x2 = x * x; return x < -3.0f ? -1.0f : x > 3.0f ? 1.0f : x * (27.0f + x2) / (27.0f + 9.0f * x2); }

CPU usage increased to 72% (I have been doing all these tests with the CPU frequency scaling governor set to performance mode so that figures are comparable). I tried g++-8 instead of g++-6, CPU usage reduced to 68%. I tried clang++-3.8 and clang++-6.0, which involved some invasive changes to replace 'c?a:b' (over vectors) with a 'select(c,a,b)' template function, but CPU usage was over 100% with both versions. So I stuck with g++-8.

The last thing I did was an algorithmic improvement: I was doing 4x the necessary amount of work in one place. Each of 4 "rules" gets fed through 4 "edges" per rule, and each edge pitchshifted its rule down an octave. By shifting (ahem) the pitchshifting from being internal to the edge to being internal to the rule, I saved 15% CPU (relative) 10% CPU (absolute), now there are only 8 pitchshifting delay lines instead of 32.

In conclusion, today I brought down the CPU usage of graphgrow-audio down from "way too high to run in realtime", to "60% of 1 core", benchmarking on an Intel Atom N455 1.66GHz netbook running Linux in 32bit (i686) mode. A side channel may be lurking, as the CPU usage (in htop) of graphgrow-audio goes up to 80% temporarily while I regenerate my blog static site...

]]>The Burning Ship fractal is interesting because it has mini-ships among filaments. However, some of these filaments and mini-ships are highly stretched or skewed non-conformally, which makes them look ugly. Some fractal software (Kalles Fraktaler, Ultrafractal, ...) has the ability to apply a non-uniform scaling to the view, which unstretches the filaments and makes (for example) the period-doubling features on the way to the mini-ship appear more circular. However, to my knowledge, all these software require manual adjustment of the unskewing transformation, for example dragging control points in a graphical user interface.

The size estimate for the Burning Ship mini-sets is based on the size estimate for the Mandelbrot set. The Burning Ship iterations are not a well behaved complex function, so Jacobian matrices over real variables are necessary. Generic pseudo-code for the size estimate of an arbitrary function:

d := degree of smallest non-linear power in f a, b := coordinates of a mini-set with period p x, y := critical point of f (usually 0, 0) L := I B := I for j := 1 to p - 1 x, y := f(x, y) L := Jf(x, y) B := B + L^{-1} size := 1 / sqrt(abs(det(L^(d/(d-1)) B)))

In particular for the power 2 Burning Ship this resolves to:

d := 2 x := 0 y := 0 L(lxx, lxy; lyx, lyy) := (1, 0; 0, 1) B(bxx, bxy; byx, byy) := (1, 0; 0, 1) for j := 1 to p - 1 x, y := (x^2 - y^2 + a, abs(2 x y) + b) L := ( 2 x lxx - 2 y lyx , 2 x lxy - 2 y lyy , 2 sgn(x) sgn(y) (x lyx + lxx y) , 2 sgn(x) sgn(y) (x lyy + lxy y) ) detL := lxx * lyy - lxy * lyx B := ( bxx + lyy / detL , bxy - lyx / detL , byx - lxy / detL , byy + lxx / detL ) detL := lxx * lyy - lxy * lyx detB := bxx * byy - bxy * byx size := 1 / sqrt(abs(detL^2 * detB))

Note the renormalization of the \(c=a+bi\) plane by:

\[L^\frac{d}{d - 1} B\]

The size estimate takes out the uniform scaling factor from this 2x2 matrix, but it's also possible to use it to extract rotation and non-uniform scaling (stretching, skew) parameters. A problem comes when trying to raise the matrix \(L\) to a non-integer power when \(d > 2\), so I fudged it and hoped that the non-conformal contribution from \(L\) is non-existent, and just used \(B\). To cancel out the uniform scaling from \(B\), I divide by the square root of its determinant, and I take its inverse to use as a coordinate transformation for the \(c=a+bi\) plane:

\[T = \left(\frac{B}{\sqrt{\det B}}\right)^{-1}\]

I tested it briefly and it seems to work for Burning Ship power 2 and 3, and also Mandelbar/Tricorn power 2. There is a problem however: to get this automatic skew matrix, one needs to zoom deep enough so that Newton's method can find a mini-set, but doing that is tricky in hard-skewed areas. So I still need to write GUI code for manual skew, to make that pre-navigation easier.

Here's just one image pair, power 2 Burning Ship, the first is with an identity transformation, the second with automatically calculated unskewing transformation:

One last thing: it should be possible to separate skew from rotation by applying polar decomposition.

]]>

Livecode Festival #2 Algorave

8pm-2am, 01 Sept. 2018

DINA, 32A-34 Cambridge St, Sheffield S1 4HP

get ticketsProbably the world's first two-room Algorave, with an international line-up of top notch live coders and algorithmic producers, including Renick Bell + Chiho Oka, Atsushi Tadokoro, Akihiro Kubota, Linux Lewis, Yaxu, Alexandra Cardenas, LiveDog Inc., Heavy Lifting, ALGOBABEZ, Qirky, Neil C Smith, Innocent, Thorsten Sideboard, Alejandro Albornoz, tedthetrumpet, Digital Selves + more ..

"The scene at an algorave is often what you'd expect from any good techno night - a dark room, engaging visuals. a decent, bass-heavy speaker set-up, and lots of people ready to dance. .. performers at algoraves respond to each other and the audience in real time, often projecting the lines of code onto the walls as they type. lt’s coding as improvisation and experiment.." - The Wire magazine

"Live coders write computer programs live, while the programs generate their music, but the focus is on people dancing and seriously enjoying hemselves" - Dazed and Confused

".. not so much a revolution as a de-volution, a rolling back to the backend of music production, where the possibilities of the encoded information inside computer software is open and endless" - Mixmag

There's also the TOPLAP Moot that weekend, I'll be talking a bit about Clive (my little thingy for audio livecoding in the C programming language), and helping people install it if they want to try it out for themselves.

]]>Last week I implemented (in Haskell, using lazy ST with each STRef paired with Natural so that I can have Ord) the algorithm presented in this paper:

Images of Julia sets that you can trust

L. H. de Figueiredo, D. Nehab, J. Stolfi, and J. B. Oliveira

Last updated on January 8, 2013 at 10:45am.

Abstract:We present an algorithm for computing images of quadratic Julia sets that can be trusted in the sense that they contain numerical guarantees against sampling artifacts and rounding errors in floating-point arithmetic. We use cell mapping and color propagation in graphs to avoid function iteration and rounding errors. As a result, our algorithm avoids point sampling and can robustly classify entire rectangles in the complex plane as being on either side of the Julia set. The union of the regions that cannot be so classified is guaranteed to contain the Julia set. Our algorithm computes a refinable quadtree decomposition of the complex plane adapted to the Julia set which can be used for rendering and for approximating geometric properties such as the area of the filled Julia set and the fractal dimension of the Julia set.

Keywords:Fractals, Julia sets, adaptive refinement, cellular models, cell mapping, computer-assisted proofs

You can find my code in my mandelbrot-graphics repository. I reproduced most of the results, I coloured with black interior, white exterior, red unknown (Julia set is inside the red region), and the quad tree cell boundaries in grey:

The last two examples above show how it fails at parabolic Julia sets.

I also implemented a trustworthy Mandelbrot set, based on the idea that if the neighbourhood of the origin in the Julia is all exterior, then the point cannot be in the Mandelbrot set, and if any interior exists in the Julia set, then the point must be in the Mandelbrot set. Now replace 'point' in those two clauses with "closed 2D square", and use the property of the algorithm in the paper that means the proofs for interiorhood and exteriorhood of the Julia set range over the interval.

It's far too slow to be practical, if pretty pictures were the goal! The red zone of unknown doesn't shrink much with each depth increment.

]]>Define the iterated quadratic polynomial:

\[ f_c^0(z) = 0 \\ f_c^{n+1}(z) = f_c(f_c^n(z))^2 + c \]

The Mandelbrot set is those \(c\) for which \(f_c^n(0)\) remains bounded for all \(n\). Misiurewicz points are dense in the boundary of the Mandelbrot set. They are strictly preperiodic, which means they satisfy this polynomial equation:

\[ f_c^{q+p}(0) = f_c^{q}(0) \\ p > 0 \\ q > 0\]

and moreover the period \(p\) and the preperiod \(q\) of a Misiurewicz point \( c \in M_{q,p} \) are the lowest values that make the equation true. For example, \(-2 \in M_{2,1}\) and \(i \in M_{2,2}\), which can be verified by iterating the polynomial (exercise: do that).

Misiurewicz points are algebraic integers (a subset of the algebraic numbers), which means they are the roots of a monic polynomial with integer coefficients. A monic polynomial is one with leading coefficient \(1\), for example \(c^2+c\). Factoring a monic polynomial gives monic polynomials as factors. Factoring over the complex numbers \(\mathbb{C}\) gives the \(M_{q,p}\) in linear factors, factoring over the integers \(\mathbb{Z}\) can give irreducible polynomials of degree greater than \(1\). For example, here's the equation for \(M_{2,2}\):

\[c^3\,\left(c+1\right)^2\,\left(c+2\right)\,\left(c^2+1\right)\]

Note that the repeated root \(0\) corresponds to a hyperbolic component of period \(1\) (the nucleus of the top level cardioid of the Mandelbrot set), and the repeated root \(-1\) corresponds to the period \(2\) circle to the left. And \(-2 \in M_{2,1}\), so the "real" equation we are interested in is the last term, \(c^2+1\), which is irreducible over the integers, but has complex roots \(\pm i\). There are two roots, so \(\left|M_{2,2}\right| = 2\).

So, a first **attempt** at enumerating Misiurewicz points works like this:

-- using numeric-prelude and MemoTrie from Hackage type P = MathObj.Polynomial.T Integer -- h with all factors g removed divideAll :: P -> P -> P divideAll h g | isZero h = h | isOne g = h | isZero g = error "/0" | otherwise = case h `divMod` g of (di, mo) | isZero mo -> di `divideAll` g | otherwise -> h -- h with all factors in the list removed divideAlls :: P -> [P] -> P divideAlls h [] = h divideAlls h (g:gs) = divideAlls (h `divideAll` g) gs -- the variable for the polynomials c :: P c = fromCoeffs [ 0, 1 ] -- the base quadratic polynomial f :: P -> P f z = z^2 + c -- the iterated quadratic polynomial fn :: Int -> P fn = memo fn_ where fn_ 0 = 0 ; fn_ n = f (fn (n - 1)) -- the raw M_{q,p} polynomial m_raw :: Int -> Int -> P m_raw = memo2 m_raw_ where m_raw_ q p = fn (q + p) - fn q -- the M_{q,p} polynomial with lower (pre)periods removed m :: Int -> Int -> P m = memo2 m_ where m_ q p = m_raw q p `divideAlls` [ mqp | q' <- [ 0 .. q ] , p' <- [ 1 .. p ] , q' + p' < q + p , p `mod` p' == 0 , let mqp = m q' p' , not (isZero mqp) ] -- |M_{q,p}| d :: Int -> Int -> Int d q p = case degree (m q p) of Just k -> k ; Nothing -> -1

This is using numeric-prelude and MemoTrie from Hackage, but with a reimplemented divMod for monic polynomials that doesn't try to divide by an Integer (which will always be \(1\) for monic polynomials). The core polynomial divMod from numeric-prelude needs a Field for division, and the integers don't form a field.

Tabulating this **attempt** at \(\left|M_{q,p}\right|\) (`d q p`

)
for various small \(q,p\) gives:

q | p | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|

1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | |

0 | 1 | 1 | 3 | 6 | 15 | 27 | 63 | 120 | 252 | 495 | 1023 | 2010 | 4095 | 8127 | 16365 | 32640 |

1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |

2 | 1 | 2 | 6 | 12 | 30 | 54 | 126 | 240 | 504 | 990 | 2046 | 4020 | 8190 | 16254 | ||

3 | 3 | 3 | 12 | 24 | 60 | 108 | 252 | 480 | 1008 | 1980 | 4092 | 8040 | 16380 | |||

4 | 7 | 8 | 21 | 48 | 120 | 216 | 504 | 960 | 2016 | 3960 | 8184 | 16080 | ||||

5 | 15 | 15 | 48 | 90 | 240 | 432 | 1008 | 1920 | 4032 | 7920 | 16368 | |||||

6 | 31 | 32 | 96 | 192 | 465 | 864 | 2016 | 3840 | 8064 | 15840 | ||||||

7 | 63 | 63 | 189 | 384 | 960 | 1701 | 4032 | 7680 | 16128 | |||||||

8 | 127 | 128 | 384 | 768 | 1920 | 3456 | 8001 | 15360 | ||||||||

9 | 255 | 255 | 768 | 1530 | 3840 | 6912 | 16128 | |||||||||

10 | 511 | 512 | 1533 | 3072 | 7680 | 13824 | ||||||||||

11 | 1023 | 1023 | 3072 | 6144 | 15345 | |||||||||||

12 | 2047 | 2048 | 6144 | 12288 | ||||||||||||

13 | 4095 | 4095 | 12285 | |||||||||||||

14 | 8191 | 8192 | ||||||||||||||

15 | 16383 |

\(|M_{0,p}|\) is known to be A000740. \(|M_{2,p}|\) appears to be A038199. \(|M_{q,1}|\) appears to be A000225. \(|M_{q,2}|\) appears to be A166920.

**HOWEVER there is a fatal flaw**. The polynomials might
not be irreducible, which means that `divideAlls`

might not be
removing all of the lower (pre)period roots! A proper solution would be
to port the code to a computer algebra system that can factor polynomials
into irreducible polynomials. Or alternatively, mathematically prove that
the polynomials in question will always be irreducible (as far as I know
this is an open question, verified only for \(M_{0,p}\) up to \(p = 10\),
according to
Corollary 5.6 (Centers of Components as Algebraic Numbers)).

You can download my full Haskell code.

**UPDATE** I wrote some Sage code (Python-based) with an
improved algorithm (I think it's perfect now). The values all matched the
original table, and I extended it with further values and links to OEIS.
All the polynomials in question are irreducible, up to the \(p + q < 16\)
limit. No multiplicities greater than one were reported. Code:

@parallel(16) def core(q, p, allroots): mpq = 0 roots = set() R.<x> = ZZ[] w = 0*x for i in range(q): w = w^2 + x wq = w for i in range(p): w = w^2 + x wqp = w f = wqp - wq r = f.factor() for i in r: m = i[0] k = i[1] if not (m in allroots) and not (m in roots): roots.add(m) mpq += m.degree() if k > 1: print(("multiplicity > 1", k, "q", q, "p", p, "degree", m.degree())) return (q, p, mpq, roots) allroots = set() for n in range(16): print(n) res = sorted(list(core([(q, n - q, allroots) for q in range(n)]))) for r in res: t = r[1] q = t[0] p = t[1] mpq = t[2] roots = t[3] print((q, p, mpq, len(roots), [root.degree() for root in roots])) allroots |= roots

**UPDATE2** I bumped the table to \(q + p < 17\). I
ran into some OOM-kills, so I had to run it with less parallelism to get
it to finish.

**UPDATE3** I found a simple function that fits all the
data in the table, but I don't know if it is correct or will break for
larger values. Code (the function is called `f`

):

import Math.NumberTheory.ArithmeticFunctions (divisors, moebius, runMoebius) -- arithmoi import Data.Set (toList) -- containers mu :: Integer -> Integer mu = runMoebius . moebius mqps :: [[Integer]] mqps = [[1,1,3,6,15,27,63,120,252,495,1023,2010,4095,8127,16365,32640] ,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] ,[1,2,6,12,30,54,126,240,504,990,2046,4020,8190,16254] ,[3,3,12,24,60,108,252,480,1008,1980,4092,8040,16380] ,[7,8,21,48,120,216,504,960,2016,3960,8184,16080] ,[15,15,48,90,240,432,1008,1920,4032,7920,16368] ,[31,32,96,192,465,864,2016,3840,8064,15840] ,[63,63,189,384,960,1701,4032,7680,16128] ,[127,128,384,768,1920,3456,8001,15360] ,[255,255,768,1530,3840,6912,16128] ,[511,512,1533,3072,7680,13824] ,[1023,1023,3072,6144,15345] ,[2047,2048,6144,12288] ,[4095,4095,12285] ,[8191,8192] ,[16383] ] m :: Integer -> Integer -> Integer m q p = mqps !! fromInteger q !! fromInteger (p - 1) f :: Integer -> Integer -> Integer f 0 p = sum [ mu (p `div` d) * 2 ^ (d - 1) | d <- toList (divisors p) ] f 1 _ = 0 f q 1 = 2 ^ (q - 1) - 1 f q p = (2 ^ (q - 1) - if q `mod` p == 1 then 1 else 0) * f 0 p check :: Bool check = and [ f q p == m q p | n <- [1 .. 16], p <- [1 .. n], let q = n - p ] main :: IO () main = print check

**UPDATE4** Progress! I found a paper with the answer:

Misiurewicz Points for Polynomial Maps and Transversality

Benjamin Hutz, Adam Towsley

Corollary 3.3.The number of \((m,n)\) Misiurewicz points for \(f_{d,c}\) is \[ M_{m,n} = \begin{cases} \sum_{k \mid n} \mu\left(n \over k \right) d^{k-1} & m = 0 \\ (d^m - d^{m-1} - d + 1) \sum_{k \mid n} \mu\left(n \over k \right) d^{k-1} & m \ne 0 \text{ and } n \mid (m - 1) \\ (d^m - d^{m-1}) \sum_{k \mid n} \mu\left(n \over k \right) d^{k-1} & \text{otherwise} \end{cases} \]

They have \(f_{d,c}(z) = z^d + c\), so this result is more general than the case \(d = 2\) I was researching in this post. The formula I came up with is the same, with minor notational differences.

]]>If you're near Split, Croatia this August you can check out the Sounding DIY exhbition, which features an updated version of my 2008 work Puzzle:

Sounding DIY

Tin Dožić, Claude Heiland-Allen, Noise Orchestra, Bioni Samp, Hrvoje Hiršl / Davor Branimir Vincze

Aug 6th 2018. - Aug 22nd 2018.

Curator/s: Darko Fritz, Laura Netz

Supported by: Ministarstvo kulture RH, Zaklada Kultura nova

MKC, Ulica slobode 28, Split

opening and live acts 6th August at 21 h

live acts by Hrvoje Hiršl / Davor Branimir Vincze and Tin Dožić.

Monday-Saturday 10-13 h, 17-21 h

free entrance

I didn't manage to get it running properly on an RPi, otherwise I could have sent just an SD card filesystem image, so it'll be running on my old 2006-era laptop, which I hope will survive the travel (both ways).

]]>