Source code for the core formula definition:

```

#donotrun

/*

Experimental Magnet-Mandelbrot bulb triplex fractal.

Type 1: z_{n+1} = [(z_n^2 + c-1) / (2z_n + c-2)]^2

*/

#define MAGNETBULB(TRIPLEX, VEC, real) \

void Magnetbulb1Triplex(inout VEC w, in VEC c) \

{ \

TRIPLEX one = TRIPLEX(real(1), real(0), real(0)); \

TRIPLEX two = TRIPLEX(real(2), real(0), real(0)); \

TRIPLEX d = TRIPLEX(c.v[0], c.v[1], c.v[2]); \

TRIPLEX z = TRIPLEX(w.v[0], w.v[1], w.v[2]); \

z = sqr(div(add(sqr(z), sub(d, one)), add(add(z, z), sub(d, two)))); \

w.v[0] = z.x; \

w.v[1] = z.y; \

w.v[2] = z.z; \

w = add(w, c); \

}

MAGNETBULB(Triplexfx, Vec3fx, floatx)

MAGNETBULB(TriplexDual3f, Vec3Dual3f, dual3f)

#undef MAGNETBULB

```

The rest of the framework is Too Big To Toot.

#Magnet #Mandelbrot set #fractal mashed up with #ThreeD #Triplex algebra (a la #Mandelbulb) rendered with #DualNumber #DistanceEstimation in #FragM fork of #Fragmentarium

My highly over-engineered extravagant framework of shaders including each other multiple times with different things defined (to emulate C++ templates with #GLSL function overloading without polymorphism) takes significantly longer to link the #shader than it does to render the #animation.

First attempts with typos gave 100k lines of cascaded errors in the shader info log, which which the Qt GUI list widget was Not Happy At All. Luckily the log went to stdout too, so I could pipe to a file and see the start where I missed a return statement or two.

Example: single ray .(001001010) = t

Images under angle doubling:

.(000100101)

.(001001010) = t

.(001010001)

.(010001001)

.(010010100)

.(010100010)

.(100010010)

.(100101000)

.(101000100)

The smallest ("characteristic") sector between neighbouring rays is at (t-,t+) = ((001001010), .(001010001)), and t = t- so it is the lower ray of a satellite component of ray period r p = 9. (afaict, If the characteristic sector didn't include t as an endpoint it would be a primitive component and the other ray would need to be found another way)

t- and t+ land together, and t+ = 2^3 t- (mod 1), so p = 3 and k = 1. The valence v = 3 because r p = 9 and v = r > 1 for a satellite component. The other ray in A_1 is 2^3 t+ (mod 1). The combinatorial rotation number is k/v = 1/3. The component's angled internal address is 1 ->_{1/3} 3 ->_{1/3} 9

Example: ray pair (.(01110), .(10001)).

Under angle doubling there are 10 rays total, so v = 2 and p = 5. The lower ray has period 5 under angle doubling, so r p = 5, so r = 1, so this is a primitive component of period 5. Consider the surrounding lower period ray pairs (wakes) that separate this component from the parabolic root at c = 1/4:

Period 4: (.(0110), .(1001)) which is the satellite component S(1/2) * S(1/2) (note that .(0111), .(1000) is inside the period 5 wake, not outside)

Period 3: (.(011), (.100)).

Period 2: (.(01), .(10)) = S(1/2)

Period 1: (.(0), .(1))

Using the notation of Dierk Schleicher's "angled internal addresses", this component is called

1 ->_{1/2} 2 ->_{1/2} ->_{1/2} 3 ->_{1/2} 5

Example: ray pair (t--, t+) = (.(001010), (010001))

The lower ray t- has 6 images under angle doubling, so r p = 6. This collection includes t+.

The forward orbit of {t-, t+} \subset A_1 gives 3 distinct sets, consistent with p = 3. This makes r = 2, and thus this is a satellite component with v = r > 1, and v = 2 means A_1 = {t-,t+}.

Numbering the angles in A_1 = {t-,t+} as 0 <= t- = t^(1) < t^(2) = t+ < 1, one sees that 2^p t^(j) = t^(j+k) = t^(j+1), working in R/Z for ray angles and (mod v) for superscripts. Thus the combinatorial rotation number is k/v = 1/2,

So the ray pair (t-,t+) lands on the root of the 1/2-satellite of a period 3 component.

The nearest surrounding ray pair of period 3 is (.(001), .(010)). Under angle doubling, there are a total of v' p' = 3 rays in the orbit portrait, and v' >= 2 as the rays land in (at least) pairs, so v' = 3 and p' = 1 by basic properties of integer divisibility. So A'_1 = { .(001), .(010), .(100) }, from which ordering the combinatorial rotation number is seen to be 1/3.

So (.(001010), (010001) lands on the root of the 1/2-satellite of the 1/3-satellite of the period 1 cardioid.

In fact I constructed the initial ray pair by tuning: S(1/2) * S(1/3), so the result is verified.

> John Milnor

> Abstract: A presentation of some fundamental results from the Douady-Hubbard theory of the Mandelbrot set, based on the idea of "orbit portrait": the pattern of external rays landing on a periodic orbit for a quadratic polynomial map.

Howdy folk! It's that time again, where I need folks help to steal digital textbooks:

Oliver, Gillian & Ross Harvey. Digital Curation 2nd edition. 2016.

Prom, Christopher J., ed. Digital Preservation Essentials. 2016.

Shallcross, Michael and Christopher J. Prom, eds. Appraisal and Acquisition Strategies. 2016.

Boosts encouraged, thanks!

current progress, 4 colour conversion in GIMP, then exported a layer for each colour, then potrace command line for each layer and assembled the output into a single SVG with a bash script.

Show thread

Seems curremt GIMP can do Gaussian blur on indexed colour images. I'm sure this used to just give errors, and you'd have to convert to RGB and back manually.

potrace DPI and SVG pattern scale interaction is annoying. Works fine at 100dpi (eg 640x480 at ~16cm wide), but with larger resolution images (more pixels, higher DPI, same output size) the SVG patterns get really tiny. So I will need to scale all the patterns to compensate.

Today this project is morphing into a generalized "make nice lo-colour/flat/line art from images" thing.

Two photos that my algorithms (currently a modified median cut that splits the largest group each time, where size is measured by the product of bounding box volume and pixel count) struggle with at the moment:

- the left image has large flat areas of almost same colour, and somehow my code dithers the top half in 3 colours instead of a nice clean split with 2 colours

- the right image has lots of greens and some "obvious" bright red, but limited to 8 colours my algorithms just give greens, no red.

GIMP does much better for both images, so next I'll look at how it does it.

Pattern fills in PDF output by `inkscape` are broken somehow. They disappear at low zoom in Okular and are not printed.

Using `rsvg-convert` seems to work better.

```

rsvg-convert input.svg --dpi-x 72 --dpi-y 72 -f pdf > output.pdf

```

DPI setting is necessary to get correct output page size, despite the SVG being measured in pt as output by potrace.

continous coupled cellular automaton in OpengGL/GLSL exported to full colour RGB24 PPM image, with edge detection

median cut in C exporting each of the colours to its own black on white PGM image (this one had 5 colours)

potrace from bi-level bitmap to monochrome vector SVG image, one per colour

geany text edited the 5x SVGs into one SVG document with some pattern fill defs, changed the fill of each layer to one of the patterns, added stroke.

inkscape converted to PDF

okular displayed PDF

gimp took screenshot

TODO: shell script to automate it

TODO: make more patterns to fill with

Implemented median cut colour quantization as described at https://en.wikipedia.org/wiki/Median_cut

The only non-straightforward part was needing to split at the boundary between median and next value, rather than (potentially) in the middle of a joint median region, to avoid weird artifacts where different parts of the image had different colours.

Hardcoded to 8 colours for now, not sure how to do optimal palette size choice with this method. Maybe https://en.wikipedia.org/wiki/Akaike_information_criterion or similar could work. But I'd need to redo the code structure, currently it's simply recursive (binary splitting, depth 3) and I'd need it to be iterative with a set of blocks to be able to make the decisions about when to stop splitting.

Show thread

Oh. I think the model is inappropriate for this use case - the mixture of Gaussians means some clusters can be very wide and flat which means the center is not a good approximation of the colour for most of the region... no wonder my results were a bit all over the place...

Hmm it's very sensitive, these are all with the same input, only thing changing is the pseudo-random number generator seed... various numbers of colours are chosen as optimal, ranging from 2 to 11.

Probably I should minimize over some randoms in an innerer loop instead of outside the whole process.

Show thread

Some strange colour choices though, the dark green on the waves seems too dark, and merged with the black for boundaries. Not sure why. Maybe just bad luck in the random number generation found a poor local optimum.

Worked (almost) first try! (after fixing compilation errors, one stupid typo in a variable name, and having to set ulimit -s unlimited in my shell because I have Big arrays on the stack).

Show thread

#AmReading Wikipedia about Expectation Maximization Algorithm for Gaussian Mixture https://en.wikipedia.org/wiki/Expectation%E2%80%93maximization_algorithm#Gaussian_mixture . I think the 4 update equations for T, \tau, \mu, \Sigma in the example section should be sufficient for me to implement it. Generalizing from 2 to arbitrary count should be simple enough too.

I want to implement this as part of a colour quantization algorithm, the other part of the problem I'll try using the Jump Method for determining optimal number of colours, the pseudo code on Wikipedia is fairly clear: https://en.wikipedia.org/wiki/Determining_the_number_of_clusters_in_a_data_set#An_information%E2%80%93theoretic_approach

making art with maths and algorithms

Joined May 2018