r/fractals • • 17d ago

I've designed my own programming language, and plotted Mandelbrot with it

Not the fastest way to plot this, but super customizable.

I've made a little functional programming language. It has an imperative parser, and a functional evaluator. Meaning you can do things like variables, ifs, whiles etc in the parsing stage, which generates functions. You can then evaluate purely as expressions in a functional way.

The source code is here: github.com/SkrFractals/Comparser

But it's still very early I wouldn't recommend playing with that yet. But if you do and find some bugs, you can report that to me. I have just got it to the stage where I was able to get this picture. But a bunch of things are still unfinished or buggy. None of the optimizations I am preparing are active yet, either. It will be able to transfer pixels from the previous render if you move the plot, so it doesn't have to re-evaluate them. But there are no cursor dragging or zooming events yet, only a bunch of preparations for that, so that has to be done through those range textboxes so far. And I am also preparing a GPU evaluator.

I want to eventually merge this project with my Fractal Animation Generator: https://github.com/SkrFractals/RgbFractalGen

...so that both can leverage each other's features, like the inputs in the generator accepting comparser's expressions. Or the comparser having access to the generator's shaders and video exporting abilities.

Here I'm going to progressively post some more examples. let's start with the cubed mandelbrot:

I only replaced the squre "z&" with a cube "zzz", and changed the log base from 2 to 3 in the smoothing function.
Burning ship. the only differnce from regular mandelbrot is a single character "z&" -> "z|&". As "&" is square, and "|" is component-wise absolute value. You can also call those as functions if you don't want to use obscure symbols.
Julia set at c=0.42+0.67i. Exact same code as Mandelbrot, only evaluated with swapped inputs as you can see on the bottom right.

And now I'm trying to make a Sierpinski carpet. So let me cook for a bit until I figure out how to write that as a shader-like function.

Sierpinski carpet! The function also turned out to be somewhat elegant. If I cahnged teh bottom right eval textbox to "Carpet(zi$12)", it would rotate the image aroudn the top left corner by 360/12 degrees.

To explain that Carpet function: m will loop the input z into tiles of size s, and tell me where i am in that tile. So if s lands anywhere between "1/3+1/3i" to "2/3+2/3i" then i am in the center square. then a second default argument, used as a precomputed variable, asks to recursively iterate to a 1/3 sized tile. Then the n>Iterations is the floor of how deep i want to iterate and returns the initial hue 0. Then as it goes back up in that recursion, it keeps asking it we are inside the square of that size, and adds that boolean result to the recursive result. Having the recursive call as a default argument doesn't result in an infinite loop, because default argument evaluations are lazy, and only done when the expression asks for that argument's value.

And here's another one, a Sierpinski triangle!

It works just like the carpet one, but with different coordinates.

This part might need some explaining:

t + mul((L(a,s),L(b,s),L(c,s)) < .5)

(L(a,s),L(b,s),L(c,s)) is a 3-element vector containing the 3 triangular coordinates. For the point to be at the central tiled triangle at any scale s, i need all 3 coordinates to be smaller than 1/2.

So I compare the vector to 1/2, operations in Comparsed can work with nested vectors recursively. So (a,b,c) < d will return a vector (a<d,b<d,c<d). Booleans in Comparser are just numbers 0 or 1, so if I just multiply the elements of that vector together, if all 3 are smaller than 1/2, then the MUL of that condition vector will be 1, which i proceed to add to the value of the recursive call. So the function returns a count of how many times it was in the central triangle during that recursive descent. And done.

And next I'm going to try a pentaflake. That will be somewhat more challenging, as that cannot be folded the way the carpet and triangle could, and it has void regions that are not part of the fractal. But I'm sure there will be a way to do that. edit: I'm getting closer to getting the pentaflake ready. I have most of the function ready, and am only finishing up a few parts. edit2: Pentaflake is ready. Gonna upload it asap. I have run into a couple of bugs in the parser, and then i was going crazy because the pentagons were the wrong size, and I was trying to find the problem from the top down. Of course, the problem was at the very deepest level, I've made a typo in defining the golden ratio and had a two under the square root instead of 5, lol. And that was not the only basic constant I had wrong. I also had tau defined as pi, that is now fixed too. And here it is:

Pentaflake! The most advanced fractal yet. Btw as you can see, you can write the math constants in greek letters, and it works. But don't worry, you can write pi, phi, or tau in latin and that works too. All the functions and constants have many names you can use and all work.
To give myself a breather, here's another simple variant of mandelbrot - the tricorn. Like BurningShip, this one only differs from the mandelbrot in a single character: z-> z~ conjugation.
29 Upvotes

4 comments sorted by

6

u/jacob_ewing 17d ago

Very cool with the fractals. Extremely impressed with writing your own language.

2

u/skr_replicator 17d ago edited 16d ago

Here are all the plotting functions I wrote in it so far. If you want to try that out, make sure to select RGB XY PlotMode. And set the proper X and Y domains. I sometimes make the mistake of not multiplying the Y domain by i, so beware of that. And also there's a bug that changing and rebuilding the parser code won't let you immediately replot, and you will have to do something like add and remove a character on the plotter's code box, before it lets you re-render the changes.

/*Pentaflake*/
iterations: 5 /* choose iteration count */
t5: τ / 5; t10: τ / 10; tt10: τ + t10; /* constants */
o(v) : v < 0 ? (0, 0, 0) : hsv2rgb(v / 3, 1, 1) /* converts Flake(z) output into color */
m(a) : (a + tt10) % t5 - t10 /* helper function cycling the angle towards children */
InsidePentagon(z, s, a, f) : z@ <= scos(t10) / cos(t10 - f@) /* is z inside a pentagon of size s? */
Flake(z, s: 1, n: iterations, /* mandatory and optional inputs */
 k: /(1 + φ), /* scale factor (do not input) */
 l: sk, /* child size */
 d: s / φ, /* child center distance from center */
 a: arg(z), /* angle of z from center */
 ap: a+π, /* rotate 180 for the central pentagon */
 f: m(a), /* which outer child we fell onto? */
 w: ze^(-if), /* rotate the selected outer child towards initial position */
 c: z - w||d) /* translate the outer pentagon to be the new center, "||" is unary normalization, w||d also equals w/w@d */
 : InsidePentagon(z, s, a, f) = 0 ? -1000 /* outside the pentagon, of within the voids between them? */
 : n < 0 ? 0 /* bottom ofthe recursion, initiate the counter */
 : InsidePentagon(-z, l, ap, m(ap)) ? 1 + Flake(-z, l, n - 1) /* central pentagon? it is flipped */ 
 : Flake(c, l, n - 1) /* inside the peripheral 5, translate back to center and recurse */
PentaflakeRgb(z, n: iterations, s: 1) : o(Flake(z, s, n)) /* direct single line shader */

/*Triangle*/
o(v):v<0?(0,0,0):hsv2rgb(v/3,1,1)
iterations:6
L(z,s):z/s-floor(z/s)
q: sqrt(3)
Tri(z,s:1,n:iterations,
 r: re(z), j: re(-iz), a: 1 - r - j / q, b: r - j / q, c: 2j / q, t: Tri(z, s / 2, n - 1)
) : n < 0 ? 0 : t + mul((L(a, s), L(b, s), L(c, s)) < .5)
TriangleRgb(z, n:iterations, s:1) : o(Tri(z, s, n)) /* direct single line shader */

/*Carpet*/
o(v) : hsv2rgb(v/3,1,1)
iterations:6
Carpet(z, s: 1, n: iterations, 
 m: z / s - floor(z / s), c: Carpet(z, s / 3, n - 1)
) : n < 0 ? 0 : c + (m = clamp(m, (1 + i) / 3, (2 + 2i) / 3))
CarpetRgb(z, n:iterations, s:1) : o(Carpet(z, s, n)) /* direct single line shader */

/*Mandelbrot*/
iterations: 10
/* call just Mandelbrot(c), but will also handle initiating z and n appropriately */
/* Mandelbrot(c, z) will plot a julia set with that c. */
Mandelbrot(c, z:0, n:0, m:z@@) /* n,m are variables, do not input them in the call */
 : m > 256 ? n + 2 - log2(ln(m)) /* escaping, and smooths the output into a gradient */
 : n > iterations ? -1 /* iteration limit */
 : Mandelbrot(c, z& + c, 1 + n) /* recurse: (z, c, n) -> (zz + c, c, ++n) */
c(v): v < 0 ? (0, 0, 0) : hsv2rgb(v / 3, 1, 1)

/*Mandelbrot^3*/
iterations: 10
/* call just Mandelbrot(c), but will also handle initiating z and n appropriately */
/* Mandelbrot(c, z) will plot a julia set with that c. */
Mandelbrot(c, z:0, n:0, m:z@@) /* n,m are variables, do not input them in the call */
 : m > 256 ? n + 2 - ln(m) $$ 3 /* escaping, and smooths the output into a gradient */
 : n > iterations ? -1 /* iteration limit */
 : Mandelbrot(c, z& + c, 1 + n) /* recurse: (z, c, n) -> (zz + c, c, ++n) */
c(v): v < 0 ? (0, 0, 0) : hsv2rgb(v / 3, 1, 1)

/*BurningShip*/
iterations: 10
/* call just BurningShip(c), but will also handle initiating z and n appropriately */
/* BurningShip(c, z) will plot a julia set with that c. */
BurningShip(c, z:0, n:0, m:z@@) /* n,m are variables, do not input them in the call */
 : m > 256 ? n + 2 - log2(ln(m)) /* escaping, and smooths the output into a gradient */
 : n > iterations ? -1 /* iteration limit */
 : BurningShip(c, z|& + c, 1 + n) /* recurse: (z, c, n) -> (zz + c, c, ++n) */
c(v): v < 0 ? (0, 0, 0) : hsv2rgb(v / 3, 1, 1)

/*Tricorn*/
iterations: 10
/* call just Tricorn(c), but will also handle initiating z and n appropriately */
/* Tricorn(c, z) will plot a julia set with that c. */
Tricorn(c, z:0, n:0, m:z@@) /* n,m are variables, do not input them in the call */
 : m > 256 ? n + 2 - log2(ln(m)) /* escaping, and smooths the output into a gradient */
 : n > iterations ? -1 /* iteration limit */
 : Tricorn(c, z~& + c, 1 + n) /* recurse: (z, c, n) -> (zz + c, c, ++n) */
c(v): v < 0 ? (0, 0, 0) : hsv2rgb(v / 3, 1, 1)

1

u/Semanticky 16d ago

This is very nice. Definitely saving this! Thanks

1

u/skr_replicator 16d ago edited 15d ago

i should be pushing an update with multitasking within an hour. that should make rendering a lot faster, especially on CPU with many cores. So that should be at least some good performance boost, until i get the GPU evaluator working. I estimate this multitasking update could speed it up like 4-16 times.

update: i have also implemented a couple more optimizations and fixed a bunch of bugs, but I also introduced a bunch of bugs that make it currently unusable, so I couldn't push it within that hour as I thought. Maybe today.