When I talk tree of functions, I mean that I have a collection of general black-box type function "pieces" that I hook together to create more complex functions. Some examples:

This is an image of a gradient function.

It is set-up like so:

y=anl.CYAMLTreeContainer()
y:gradient("Grad1", 0,1,0,0,0,0)

It is a black-box function that sets up a gradient based on an input line segment. In the above example, the line segment is oriented from (x=0,y=0,z=0)->(x=1,y=0,z=0). Any input coordinate is projected onto this line and assigned a value based on where it projects to. Values that lie less than or equal to the first line segment end-point map to the value of 0, values that lie greater than or equal to the second end-point map to 1, and values in between map to some value on the gradient from 0 to 1. In the above image, I am mapping a 2D range from (x=0,y=0,z=0) to (x=1,y=1,z=0) to get the final image.

Now, I can take the output of the gradient function and use it as the input for another function, say a function to multiply it by 36:

y:gradient("Grad1", 0,1,0,0,0,0)
y:math("Math1", anl.MULTIPLY, "Grad1", 36.0)

Result:

You can see that the gradient section is now squeezed into a smaller portion of the image. I can take that signal and pass it along to another function; say, a cosine function:

y:gradient("Grad1", 0,1,0,0,0,0)
y:math("Math1", anl.MULTIPLY, "Grad1", 36.0)
y:math("Math2", anl.COS, "Math1", 0.0)

Now, let's get a little bit more complex by setting up a fractal and using it to translate the x input coordinate to the cosine function:

y:gradient("Grad1", 0,1,0,0,0,0)
y:math("Math1", anl.MULTIPLY, "Grad1", 36.0)
y:math("Math2", anl.COS, "Math1", 0.0)
y:fractal("Fractal1", anl.FBM, anl.GRADIENT, anl.QUINTIC, 8, 3)
y:autoCorrect("AutoCorrect1", "Fractal1", -0.25, 0.25)
y:translateDomain("Translate1", "Math2", "AutoCorrect1", 0.0, 0.0)

This function chain sets up a fractal and a translateDomain function, sets the fractal to be the X-axis input, and the output of the cosine module to be the source input. The TranslateDomain module has the effect of translating the input coordinate to the function using the values obtained from the X, Y and Z input functions. In this case, when you call the function with a given (X,Y,Z) coordinate, it will first call the AutoCorrect1 function (which is a helper function attached to Fractal1 that has the effect of re-mapping the output of Fractal1 into a more useful range, in this case (-0.25, 0.25)) to obtain the value of the fractal at (X,Y,Z), then it will add this result value to the X component of the input coordinate, and use the new coordinate to call Math2 and get the value of the cosine function chain. The result looks like the classic turbulence marble effect you often see in beginner texts on procedural textures:

Now, this is still outputting values in the range (-1,1) so anything that drops below 0 is clamped to 0, meaning a lot of the texture is black. Let's send the output to another module to correct it to the range (0,1):

y:gradient("Grad1", 0,1,0,0,0,0)
y:math("Math1", anl.MULTIPLY, "Grad1", 36.0)
y:math("Math2", anl.COS, "Math1", 0.0)
y:fractal("Fractal1", anl.FBM, anl.GRADIENT, anl.QUINTIC, 8, 3)
y:autoCorrect("AutoCorrect1", "Fractal1", -0.25, 0.25)
y:translateDomain("Translate1", "Math2", "AutoCorrect1", 0.0, 0.0)
y:scaleOffset("ScaleOffset1", "Translate1", 0.5, 0.5)

result:

Now, just for giggles, let's duplicate that 3 whole set 3 times, and use all 3 as the Red, Green and Blue components of a RGB color composing function:

y:gradient("Grad1", 0,1,0,0,0,0)
y:math("Math1", anl.MULTIPLY, "Grad1", 36.0)
y:math("Math2", anl.COS, "Math1", 0.0)
y:fractal("Fractal1", anl.FBM, anl.GRADIENT, anl.QUINTIC, 8, 3)
y:autoCorrect("AutoCorrect1", "Fractal1", -0.25, 0.25)
y:translateDomain("Translate1", "Math2", "AutoCorrect1", 0.0, 0.0)
y:scaleOffset("ScaleOffset1", "Translate1", 0.5, 0.5)
y:gradient("Grad2", 0,1,0,0,0,0)
y:math("Math3", anl.MULTIPLY, "Grad2", 36.0)
y:math("Math4", anl.COS, "Math3", 0.0)
y:fractal("Fractal2", anl.FBM, anl.GRADIENT, anl.QUINTIC, 8, 3)
y:autoCorrect("AutoCorrect2", "Fractal2", -0.25, 0.25)
y:translateDomain("Translate2", "Math4", "AutoCorrect2", 0.0, 0.0)
y:scaleOffset("ScaleOffset2", "Translate2", 0.5, 0.5)
y:gradient("Grad3", 0,1,0,0,0,0)
y:math("Math5", anl.MULTIPLY, "Grad3", 36.0)
y:math("Math6", anl.COS, "Math5", 0.0)
y:fractal("Fractal3", anl.FBM, anl.GRADIENT, anl.QUINTIC, 8, 3)
y:autoCorrect("AutoCorrect3", "Fractal3", -0.25, 0.25)
y:translateDomain("Translate3", "Math6", "AutoCorrect3", 0.0, 0.0)
y:scaleOffset("ScaleOffset3", "Translate3", 0.5, 0.5)
y:rgbaCompositeChannels("RGBA", "ScaleOffset1", "ScaleOffset2", "ScaleOffset3", 1.0, anl.RGB)

Result:

The three sub-chains are identical copies of one another, and differ only in the seed used. But there is no real need to duplicate them. Each of the inputs, Red, Green, Blue and Alpha, used by the rgbaCompositeChannels() function could be completely different functions.

By chaining modular functions together in this manner, you can build complex effects out of simple building blocks.

To build modules, I can either hard-code them as in the above examples, or I can write a Lua script that will recursively generate a randomized tree. It starts at the root and chooses a function randomly from a table of possible functions. Then for each input of the chosen function, additional functions are chosen randomly from a table. This continues on up the tree until I hit some specified depth, at which point some leaf-node functions are randomly selected. These, of course, would be generators such as fractal(), which wouldn't have higher input functions.

By studying the results of such a randomized tree I can get a feel for how certain functions will behave, and I can store the generated trees away for future reference.