How to write formulas. Short tutorial for Mikko written by Andreas Lober. March 05, 2000 I am not a formula designer like Samuel Monnier. I do not know why the Sierpinksi algorithm makes the triangle structure or why his Koch Curve formula works. This is not my part of knowledge. Perhaps I will understand it later. I am not experienced as Damien Jones or Sylvie Gallet or Mark Townsend or ... I am able to structure my little "programs", to use and re-use "modules" with certain "interfaces". I am always curious to see what kind of image results from my formula. I like to play with a formula, I like to have many knobs and buttons. For example: I could split up the colouring formula "Metrics and Watches" into many single and different colourings. But if I had a new idea for it, I would have to apply it to those many colourings. For that I am much too lazy. Curiosity and laziness are the two main technical impetus of mankind ;-) Often I just try one fractal image with my new formula, post it as an example. If someone likes the new toy, then it is okay - then I'm happy. Then it is time to create a new formula. Perhaps writing a formula is THE creative act - for me. Others can create many beautiful images that come from a very few number of formulas... In this little text I will describe two things: A) How I found Hevia and how it is built. B) How is the structure of "Metrics and Watches" and how can you use it. C) Something about the toolbox. I give a quick insight and shorten the examples. More details can be found in the formula files. All the param blocks belong to the default section. ___________________________________________ | | | A) How I found Hevia and how it is built. | |___________________________________________| * First idea: Reading about the matrix operations in Gerald's formulas. * Second idea: Imagine the Five on a die. Let the center point be z and the surrounding four points form the entries of a complex matrix M(z). Matrices "operate" on vectors in different ways. Since every complex number is a two-dimensional real vector, all my ingredients are in the right place. Now let me begin. 1. Calculate the two-dimensional real vector: x = real(z) y = imag(z) 2. Find the 4 points in the neighbourhood of z that shall form the matrix: a = floor(x) + 1i* ceil(y) b = ceil(x) + 1i* ceil(y) c = floor(x) + 1i*floor(y) d = ceil(x) + 1i*floor(y) Now bring a little more flexibility to it: if (@latticeType == 0) ; floor/ceil xf = floor(@latticeFac*x)/@latticeFac yf = floor(@latticeFac*y)/@latticeFac xc = ceil(@latticeFac*x)/@latticeFac yc = ceil(@latticeFac*y)/@latticeFac elseif (@latticeType == 1) ; flow xf = @latticeFac*x yf = @latticeFac*y xc = xf+@latticeFac yc = yf+@latticeFac endif ; matrix M(z): a = xf + 1i*yc b = xc + 1i*yc c = xf + 1i*yf d = xc + 1i*yf 3. Operations of M(z) on z resp. (x,y): zq = conj(z) ; z*zq=|z| if (@operation == 0) ; linear ; matrix algebra z = a*x + b*y + 1i*(c*x+d*y) elseif (@operation == 4) ; Scalar Product z = a*z^2 + (b+c)*z*zq + d*zq^2 endif Now the most things are ready. Let me open my formula toolbox to enhance Hevia. 4. Apply a lattice on the complex plane. In short: If you calculate z-floor(z) you get a complex number near the origin (0,0) within a rectangle of width 1. That means: Instead of calculating all the complex numbers in the complex plane you calculate the small rectangle and pave the whole complex plane with little rectangles. This should give a certain kind of periodicity. ; ; Start with the lattices ; if (@lattice == 1) z = round(z) - z elseif (@lattice == 2) z = trunc(z) - z elseif (@lattice == 3) z = floor(z) - z elseif (@lattice == 4) z = ceil(z) - z endif param lattice caption = "Lattice Type" enum = "None" \ "round" "trunc" "floor" "ceil" default = 0 ; means "None", i.e. no lattice endparam It is your choice, where to place this latice module. I place it in the beginning of the loop section. 5. Now just a few add-ons: Include a parameter exponent with default value 1.0 and use it: if (@exponent != 1) z = z^@exponent endif The transformation z -> z + 1/z is called Joukowskij Transformation. It has something to do with airplanes. Include a complex parameter jouk and use it: if (z != 0 && @jouk != 0) z = z + @jouk/z endif Remember: Multiplication with 1i means 90 degree rotation to the left. So playing with complex entries for jouk gives interesting effects. The Mandelbrot formula has the line z = z^2+#pixel. Include a boolean parameter withPixelAddition (enbled/disabled) and use it: if (@withPixelAddition) z = z + #pixel endif At last: an additional function may be applied to change all the shapes: z = fn1(z) Now Hevia is ready. 6. In most bailout sections you can read |z| < 4 or |z| < 1e20 -> huge number Why not get more flexibility as to be found in Ron Barnett's file: bailout: (@test == 0 && |#z| <= @bailout) || \ (@test == 1 && sqr(real(#z)) <= @bailout) || \ (@test == 2 && sqr(imag(#z)) <= @bailout) || \ (@test == 3 && (sqr(real(#z)) <= @bailout \ && sqr(imag(#z)) < @bailout)) || \ (@test == 4 && (sqr(real(#z)) <= @bailout \ || sqr(imag(#z)) < @bailout)) || \ (@test == 5 && sqr(abs(real(#z)) + abs(imag(#z))) <= @bailout) || \ (@test == 6 && sqr(real(#z) + imag(#z)) <= @bailout) param test caption = "Bailout Test" default = 0 enum = "mod" "real" "imag" "or" "and" "manh" "manr" endparam 7. More flexibilty in the init section: Instead of writing z = 0 or z = #pixel you can write the following: if (@initialisation == 0) ; Pixel z = #pixel elseif (@initialisation == 1) ; Init Value z = @initValue endif param initialisation caption = "Initialisation" enum = "Pixel" "Init Value" default = 0 endparam param initValue caption = "Init Value" default = (0.0,0.0) endparam If you look at the Hevia formula in akl.ufm you see a very long formula compared to some others. But most parts of it are modules from my toolbox. The "idea" inside Hevia is the part with the die. ___________________________ | | | B) The structure of | | "Metrics and Watches". | |___________________________| Most colouring formulas consist of a transformational part, a distance estimation, and a trap algorithm. 0. The Lattice is always usefull. ; ; Start with the lattices ; if (@lattice == 1) zStart = round(@lattFac1*zStart) - @lattFac2*zStart elseif (@lattice == 2) zStart = trunc(@lattFac1*zStart) - @lattFac2*zStart elseif (@lattice == 3) zStart = floor(@lattFac1*zStart) - @lattFac2*zStart elseif (@lattice == 4) zStart = ceil(@lattFac1*zStart) - @lattFac2*zStart elseif (@lattice == 5) zStart = abs(@lattFac1*zStart) - @lattFac2*zStart^2 ... endif If you want to see what these lattices do, then take the normal Mandelbrot set and apply the transformation "Alli None" from akl.uxf on it. Use transformation "None" and choose a lattice. 1. The transformational part: _________________________________ | | | C) Something about the toolbox. | |_________________________________| I use my toolbox like Wizzle uses her gradients. What is in my toolbox? All the nice transformations of Mark Townsend, the Sierpinski flavours of KPeterK, many ideas from Samuel Monnier. With every formula file I read the toolbox grows. My own ideas are in it, too ;-) My toolbox is the whole UF library except the files from Bob Carr. 1. What is AGM (Found in Stephen Wolfram "Mathematica")? AGM means Arithmetic Geometric Mean. Look at 4 and 8. AM(4,8) = 0.5*(4+8) = 6.00 GM(4,8) = sqrt(4*8) = 5.66 => |AM-GM|=0.34 Now repeat this with the above new numbers: AM(6.00,5.66) = 5.83 GM(6.00,5.66) = 5.82 => |AM-GM|=0.01 Now repeat this with the above new numbers: AM(5.83,5.82) = 5.825 GM(5.83,5.82) = 5.824 => |AM-GM|=0.001 One can see in this example: Iteration of calculating AM and GM brings their difference near zero. In the different AGM modules I calculate the number of iterations needed to bring |AM-GM| under a given limit. Afterwards I do something with this limit... ar = |@fn1(z)| ge = |@fn2(w)| ; w is something special in this formula ; choose something appropriate iter = 0 while (|ar-ge| > @epsilon && iter < @maxIter) iter = iter +1 ; Calculate new values of arithmetical ; and geometrical mean arit = (ar+ge)/2.0 geom = sqrt(ar*ge) ; give these values back ; for the next loop-run ar = arit ge = geom endwhile 2. Mark Townsend's "Harlequin" in the AKL revision: ; ; transformation ; if (@_pseudo) float _x = real(zStart) float _y = imag(zStart) if (@_pseudoMix) if (@_mixMode == 0) ; normal mix a = @m * atan(_x/_y) elseif (@_mixMode == 1) ; diff mix a = @m * atan((_x-_y)/_y) elseif (@_mixMode == 2) ; sum mix a = @m * atan((_x+_y)/_y) elseif (@_mixMode == 3) ; prod mix a = @m * atan(_x^2/_y) elseif (@_mixMode == 4) ; expo mix a = @m * atan(_x^_y/_y) endif else a = @m * atan(_y/_x) endif else a = @m * atan(zStart) endif if (@variation == 0) a = a * a + @c a = fn1(1 - a) / a elseif (@variation == 1) a = a * a + @c a = fn1(1 - a^2) elseif (@variation == 2) a = @m * fn1(a + @c)^2 elseif (@variation == 3) a = @m * fn1(1i*a + @c)^2 / fn1(a - @c) elseif (@variation == 4) a = @m * fn1(1i*a + @c)^2 * fn1(a - @c) elseif (@variation == 5) a = @m * fn1(1i*a + @c)^2 + fn1(a - @c) endif z = a 3. Modes for replacement of real/imag calculations: ; ; Which mode do you like? ; if (@realImagMode == 0) ; real/imag ; the normal mode real_z = real(z) imag_z = imag(z) elseif (@realImagMode == 1) ; sin/cos real_z = real(sin(z)) imag_z = imag(cos(z)) elseif (@realImagMode == 2) ; Arcus real_z = real(asin(z)) imag_z = imag(acos(z)) elseif (@realImagMode == 3) ; Hyperbolic real_z = real(sinh(z)) imag_z = imag(cosh(z)) elseif (@realImagMode == 4) ; ArHyp real_z = real(asinh(z)) imag_z = imag(acosh(z)) elseif (@realImagMode == 5) ; exp/log real_z = real(exp(z)) imag_z = imag(log(z)) elseif (@realImagMode == 6) ; sqr/sqrt real_z = real(sqr(z)) imag_z = imag(sqrt(z)) endif