GLNumericalModelingKit is an Objective-C framework designed to enable high performance numerical modeling of differential equations.
The primary design goal of this framework is to eliminate the tedious an error prone programming required for numerical modeling by introducing a layer of abstraction on top of the usual code. From the outset, I wanted to be able to simply write [psi x] in objective-c and have that automatically take the x derivative of my variable psi, instead of fussing with memory buffers, differentiation matrices and Fourier transforms. GLNumericalModelingKit makes that possible, but also tries to be flexible enough to accommodate the unique demands that inevitably come with each project.
The code is divided into two conceptual pieces,
CoreEquation – CoreEquation enables basic algebraic operations on multidimensional discretized variables as well as reading and writing to NetCDF and other standard formats.
CoreDiffEq – CoreDiffEq depends on CoreEquation, and enables differentiation and integration for solving partial differential equations.
CoreEquation
CoreEquation is a small framework designed for performing basic algebraic operations on discretized variables. The three most important classes GLDimension, GLVariable, and GLEquation are best understood by simple example. For this example, let’s create a Gaussian in two dimensions,
\begin{equation} g(x,y) = A e^{ -\frac{(x-x_0)^2+(y-y_0)^2}{L^2}} \end{equation}
and then write it out to a NetCDF files so that we can easily read it in Matlab.
Dimensions
The first thing to do is to define dimensions,
GLDimension *xDim = [GLDimension dimensionXWithNPoints: 256]; xDim.domainLength = 2.0; GLDimension *yDim = [GLDimension dimensionYWithNPoints: 128]; yDim.domainLength = 1.0;
We used double the number of points in the x dimension, so therefore we also double its length.
Equation
We now use this to create an equation object,
GLEquation *equation = [[GLEquation alloc] init];
[equation setDimensions: [NSArray arrayWithObjects: xDim, yDim, nil]];
It’s important to note that once you’ve set the dimensions for a given equation, you cannot change them.
Variables
Now, from the dimensions we want to create variables x and y. In code this is just,
GLVariable *x = [equation variableFromDimension: xDim];
GLVariable *y = [equation variableFromDimension: yDim];
At first this might seem confusing: why do we have a variable named x and a dimension named x!?!? The answer is that the variable x actually has two dimensions: x and y. In other words, you can think of the variable x as a two-dimensional array of values that is constant in the y direction and variable in the x direction. It doesn’t make sense to add two dimensions together, but it does make sense to add two variables together that are of the same dimension.
So now, let’s the define the constants of the problem, and create the Gaussian.
GLFloat amplitude = 1.0;
GLFloat length = 0.1;
GLFloat x0 = 0.5;
GLFloat y0 = 0.25;
GLVariable *xbar = [x scalarAdd: -x0];
GLVariable *ybar = [y scalarAdd: -y0];
GLVariable *r2 = [[xbar times: xbar] plus: [ybar times: ybar]];
GLVariable *gaussian = [[[r2 scalarMultiply: –1.0/(length*length)] exponentiate] scalarMultiply: amplitude];
This code should be fairly readable. The one catch is that, at this point in the program, the variable gaussian doesn’t actually contain any data! Internally it has simply kept track of a series of instructions on how to make the gaussian variable. To actually compute the value, we need to talk to the equation object again.
[equation solveForVariable: gaussian];
Now the gaussian variable actually contains real data. Even better, the equation parallelized the computation where appropriate. For example, the computation of \( (x-x_0)^2 \) and \( (y-y_0)^2 \) were likely done in parallel because they didn’t depend on one another. I say likely because this is all handed to grand central dispatch, which ultimately decides how things are parallelized.
Finally, to write this to file, we simply name the variable and tell it where to write,
gaussian.name = @”Gaussian”;
[gaussian writeToNetCDFFile: [NSURLURLWithString: @”/Users/jearly/Desktop/AlgebraicTest.nc”]];
The NetCDF file is written in a standard format and the variable is easily plotted.