We all know the definition of the factorial ! as but there are even more definitions of factorials that mathematicians have invented.
Double Factorial
The double factorial looks like this . It can be used to easily denote the product of the odd or even numbers less than or equal to n
Subfactorial
The subfactorial has a really intuitive practical application. Imagine you have n tokens in defined positions in an array. You take all the tokens out and want to place every token back into the array so that none of the tokens end up in the position they were just in. This is called derangement.
For instance, , You can arrange this set in the following ways: .
Only two of these sets satisfies the above criteria, and
Therefore we say
Primorial
The Primorial is denoted and is the product of primes less than and equal to n.
Primorials are used in the search for large prime numbers. Each primorial has more distinct prime factors than any number smaller than it.
Superfactorial (Sloane)
The more common definition of the Superfactorial. It is defined as such:
For example:
You can look at it another way by expanding the factorials:
And so:
Superfactorial (Pickover)
Another definition of the Superfactorial uses tetration.
It grows insanely fast.
Exponential Factorial
Annoyingly also denoted
the exponential factorial is like a normal factorial but exponentiated instead of multiplied:
Hyperfactorial
Finally the hyperfactorial is defined like this:
Or:
And hence is very similar to the Sloane definition of the superfactorial:
Fractal Flow is my largest programming project so far; here’s a little about it:
Early days
I started Fractal Flow on January 12th 2022, after a few months of learning and tinkering with the Windows Presentation Foundation in my Computer Science class. The application was to be my coursework, which makes up 20% of the AQA A-level Computer Science grade.
January was not usually the month in which my college starts pupils on the NEA (Non-Examination Assessment), however my teacher had offered that I start early due to my prior knowledge in programming. I snatched the offer, hoping I could turn this opportunity to spend hours of my time programming into something more than just my A-level coursework – I wanted to create a piece of professional software that could persist as a gem in my portfolio for years to come.
What is it?
Fractal Flow is a fractal generator. Its job is to generate and export images of fractals, geometric shapes which contain a high level of detail when zooming in and often contain entire versions of themselves.
One of the most famous fractals is the Mandelbrot Set, first visualised by Benoit Mandelbrot in 1980. This infinitely detailed object is constructed over the complex plane with the formula , a deceptively simple operation for the immense complexity which is generated.
is but one of an infinite set of formulae that can be chosen to create fractals on the complex plane. These unlimited possibilities is what Fractal Flow is designed to explore. The software is fully focused on creating fractals from any formula the user can come up with – doing so with incredible performance.
Mission
From the beginning I was laser focussed on creating a product that was not only intuitive and visually attractive, but also competently engineered behind the scenes. The first step to achieving this was abandoning what was expected of me: using WinForms. I taught myself WPF (Windows Presentation Foundation) and furthermore how to use it with a professional approach. MVVM (Model View View Model) allowed for a proper separation between the user interface and the business logic and thus opened the door to code reusability.
The MVVM approach to applications
Designing the UI
I was not happy with leaving my app with a bog-standard UI. Noticing that all the fractal generators I had tried had very tired visuals, I set about changing the expectation for how such software could look by creating a sleek, modern user interface.
This was my first time using the brilliant website Figma. I had a lot of fun designing the pages and selecting iconography and typefaces.
Rendering Kernel
The crown jewel of the application is the rendering technique I developed for it. Rather than running the computationally intense tasks in C# on the CPU, Fractal Flow hands rendering to the GPU. This allows for an enormous increase in performance (over 1000x faster in my tests).
The GPU is faster than the CPU in this field because it allows the concurrent execution of hundreds of calculations simultaneously. The colour of each pixel in these fractal images are completely independent of the other pixels in the image, so they can be treated as completely different processes to each other. This makes parallelisation really easy as each pixel can be sent as a separate job to the GPU.
To harness the power of the GPU in WPF, I used a library which provided access to OpenCL (Open Computing Language). This allowed me to provided a small piece of C code called a kernel that is to be run on the GPU. I could specify how many of these kernels are sent to the GPU and an array of arguments to be distributed between them.
This is where the clever part comes in. From the iterative formula the user specifies to generate their fractal, a line of C code representing that operation is generated. This is then implanted into the kernel to create a new program, specifically programmed to render the fractal. This approach is called metaprogramming, a technique where code writes itself.
I used this approach for technical limitations (my understanding was that using the complex number libraries in the OpenCL library kernel was unsupported so I resorted to a series of macros for my complex operations), and also because I believed that it would reap even greater computational performance over the CPU.
Diagram of render pipeline
Proprietary Formula Parser
I created my own mathematical formula parser for this application. This was not a necessary step – reinventing the wheel for such a common process is not good practice. Regardless I made a homemade parser because: 1. It was a valuable learning process and 2. It would give me marks in my NEA 3. It allowed me total control over how the parser worked.
Developing this algorithm taught me about Dijkstra‘s Shunting Yard Algorithm (yes the same Dijkstra who invented the route finding algorithm), which converted an expression in infix notation to the Reverse Polish Notation required for execution by computers.
I also made sure my algorithm allowed for relatively natural expression. For instance it accepted notation such as ““, the terse form of ““. I realised I could summarise token adjacency behaviour in a table:
Current State
I left Fractal Flow in a state I was relatively happy with, however there were still many features I envisioned for the application that have not made it to the application yet. For instance I initially wanted the application to be able to produce zooming videos, and have a multitude of different colouring algorithms as found in other fractal generators.
It was investigating colouring algorithms where I found a large problem with the code of Fractal Flow – an issue so large that it led to a downfall in motivation to develop the project. The problem was that many colouring algorithms require information *other* than simply the number of iterations each pixels took before it triggered the bail, such as the distance the iteration that triggered the bail “jumped” from the prior position. Implementing the inclusion of this information would require a large redesign of the render pipeline and considerable thought over how to architect a memory-efficient system.
I learnt an important lesson in discovering this flaw: to always research thoroughly absolutely everything my system might be implementing on top of what I already know it must.
There are also a few areas of the program that are unnecessary or artificially shoe-horned into the designed, with the purpose of hitting more targets on the NEA mark scheme. For example the formula parser discussed earlier, and the use of an SQL database to store fractal definition objects (I think simply storing them in files would be more reasonable).
Fractal Flow is currently over 10000 lines with over 20 classes.
GitHub README
FractalFlow
WIP – This is my A-level (Junior/Senior year equivalent) Computer Science coursework as submitted. REMEBER TO MAKE BRANCH IF WORKING ON FURTHER
Fractal Flow is a next generation application for rendering fractal images over the complex plane, with a focus on generating custom user-defined fractals.
Fractal Flow aims to streamline and accelerate the process of exploring new fractal objects – beyond the Mandelbrot set. Fractal Flow combines a blazing fast rendering kernel with a UI whioh empowers rapid iteration and generation of ideas.
Fractal Flow uses a meta-programming approach to achieve superior performance. The application generates an OpenCL Kernel specifically for the formula specified, which is ran on the GPU.
The application currently also includes a renderer for the CPU, however it is highly unoptimised and unfeasibly slow, and thus will no longer be maintained.
User Interface
Features
CPU & GPU rendering capabilities
Create a custom fractal by specifying a formula
Natural math expression achieved through proprietary formula parser
Two types of fractal colouring algorithm
Specify iterations, bail value and region on the complex plane
Save and load proprietary .frac fractal definition file
A differential equation is an equation which contains the derivative of one or more of the variables.
They are incredibly useful because they allow models to describe the rate of change of values rather than the size of the values.
First order Differential Equations
Some first order differential equations can simply be solved by separating variables and integrating. This is a normal maths technique so I will be skipping over it.
Linear first order differential equations are in the form:
To solve these, you can observe how an application of the product rule would have produced the expression on the left side.
For instance, take the linear first order differential equation:
Observe that by applying the product rule to the expression
Gives the same expression as on the left of the above equation, taking into account the need for implicit differentiation.
After spotting this, integrating both sides with respect to x will yield a solution, but don’t forget a + c on one of the sides!
DONE! Don’t forget to apply the division of x to the c as well.
Integration Factor
Sometimes the equation will not immediately be susceptible to the reversed product rule attack, but we can change the equation to force it to.
First, a particular expression must be found called the Integrating Factor (IF). This can be found using the formula:
Multiply the entire equation by this expression and the trick described earlier will always work.
Second Order Differential Equations
Second order differential equations are differential equations which contain a second derivative.
Homogeneous
Here is how to solve differential equations in the form:
This is called a homogeneous equation because it is equal to zero.
The first step in solving these equations is to form the Auxiliary Equation(A.E).
There are three scenarios to follow depending on the solutions to this quadratic:
Scenario 1: Distinct Real Solutions
In the case where the quadratic has two distinct real solutions, use the equation:
Scenario 2: Repeated Real Solution
Scenario 3: Imaginary Solutions
Where the roots are the form of:
These expressions are called the Complementary Function (C.F.).
Select the correct equation and sub in the roots and you have the General Solution (G.S).
Non-homogeneous
You need to add an extra step if the differential equation is not equal to 0, rather a function of x.
The first step is to get the Complementary Function like usual.
You then have to find the Particular Integral (P.I.) which satisfies the differential equation. First, observe the form of the function of x on the right side of the equation. Depending on it’s form, select on of the following forms of expression.
Form of f(x)
Form of P.I.
k
λ
ax+b
λx + μ
ax2 + bx + c
λx2 + μx + ν
mcos(ωx)
λsin(ωx) + μcos(ωx)
msin(ωx)
λsin(ωx) + μcos(ωx)
msin(ωx) + ncos(ωx)
λsin(ωx) + μcos(ωx)
Once one of these equations has been found, set it equal to y and find the first and second derivative.
Then sub these three expression into the formula. Compare coefficients to find the values of the unknowns.
You have now found the Particular Integral! Simply add this to the Complimentary Function to find the complete general solution!
The inverse of a function can be found by swapping x and ys in an equation (and then rearrange back to terms of x). This has the effect of reflecting the original function in the line y = x, as you are essentially just swapping the x and y axis.
Some mates and I sat down a few weeks ago and tried to ask the question: how do we reflect in a different line, not y = x. We set out to construct a robust description of reflection, so we could get the equation of any a reflection of any function, in any other function!
We decided that we would need to find an equation to get the gradient of a line based on two other lines: one the reflectant (which can be imagined as the incident ray) and the reflector (the mirror or boundary). I set out to find this formula and here is what I found:
Variation 1
This is the second version of the formula for the gradient of reflection (the first being a method similar to this but with an unnecessary step, making it less simplified).
The aim is to have two equations:
Reflectant: y=m1x
Reflector: y=m2x
And we want to find the gradient of the line which is formed when the reflectant is reflected in the reflector. We know from physics that the angle of incidence (the angle the reflectant makes with the normal at the point of collision with the reflector) is equal to the angle of reflection:
Angle of Incidence = Angle of Reflection
Now we’ve got that essential aspect out of the way, let me explain how I derived the formula:
First know that we can represent a line as a complex number, 1 + im, where m is the gradient of the line we want to convert to this complex representation. So for example the line y=x can be represented as the complex number 1 + i, and the line y = -3x can be represented like 1-3i.
This works when there is 1 on the real axis, as then the y value is equal to the gradient.
Now, consider this example:
The reflector line is x = 0 (lets ignore that this is not possible in the form I said the lines would be in earlier), and the reflectant is y = -x.
Imagining the reflector line as the complex number (1-i), to get the reflected line we must get it’s complex conjugate, and then convert it back into the equation of a line. So (1-i) goes to (1+i), which is the complex number which represent the line y = x, giving us our reflected line.
Okay, so lets add getting the complex conjugate to our list of things we need to do.
Second consider the setup: Reflector: y = x, Reflectant: x=0.
From now on, the mirror line will be coloured black and the light ray will be coloured yellow.
Imagining again the reflectant line as the complex number (0-i) and the reflector (1+i), lets call (0-i) z1 and (1+i) z2. When you multiply together two complex numbers, you can imagine it as two properties of the complex numbers interacting:
The Argument of a complex number is the angle the line between the point and origin makes with the positive part of the real axis.
The Modulus of a complex number can be described as the distance from the number to the origin.
When you multiply two complex numbers, the resulting complex number will have a modulus that is the product of the two original numbers, and a argument that is the sum of the two original numbers.
A way of looking at complex multiplication
Going back to our original scenario, observe that the modulus of z2 is π/4 (45⁰) and the modulus of z1 is -π/2 (-90⁰). The line we want to get is going to be y = 0, represented by the number 1 + 0i. So to get this, we need to multiply z1 by z2TWO TIMES, to get a number with an argument of 0 (-90 + 2 * 45 = 0).
Okay, so lets add multiplying by z2 two times as another one of the things we need to do.
This gives us the formula:
M1 and M2 are defined above.
Okay, lets expand this:
We now have this complex number, but its not useful to us yet, as the real part is not equal to 1.
So if we multiply the complex number by 1 / Re (z) we will scale the entire thing so the real part is 1 and the imaginary part is whatever it lines up with. Also at this point we can do a special trick, removing the i. Treat the imaginary part as the “y” and the real part as the “x”. This way the equation can be generalised to the Cartesian plane.
This gives us the final form of the equation:
The first version of the gradient of reflection formula
Variation 2
There is more that can be done – a slightly different approach…
A complex number can be written in the form
Where r is the modulus of the complex number and θ is the argument of the complex number.
So if we convert our formula at the earliest step, we get this:
We can simplify this by changing the arg functions to arctan(m1 or m2) (as the real part of z1 and z2 are just 1 and the imaginary part is simply the gradient of the lines those numbers represent). We can also use indices laws to first put the power of two into the second e’s exponent, and then add the exponents of both e factors, as they have the same base. We now get this:
It has been slightly rearranged so the negative term comes second
To go further, we must figure out how to get this formula to work on the cartesian plane, and thus must convert the formula into a complex number in the form a + bi.
To do this, we can use the mod-arg form of a complex number, so:
First, factor out i:
And see that now:
So now sub theta into the mod-arg form of the complex number:
And now we can just do what we did in the prior variation of this formula. Imagine we are scaling up the real part to 1, dragging the imaginary part along so it is the gradient of the new line. Do this by multiplying the imaginary part by 1 over the real part (dividing by real). Lets drop the i now as well:
Now you will see that this can be simplified by the trig identity tan = sin over cos, and we get the final formula!