Airy function: Second-order, linear, ordinary differential equation:
x''(t) = -t*x(t)
Initial values:
x(0) = 1
x'(0) = 0

Even though this is a simple equation, the solution contains complex airy functions.
Analytical solution: x(t) = 1/2 * 3^(1/6) * Gamma(2/3) * (sqrt(3) * Ai((-1)^(1/3)*t) + Bi((-1)^(1/3)*t))
Ai() is the Airy function
Bi() is the Airy Bi function

Airy.h

#pragma once

#include "ascent/Module.h"

class Airy : public asc::Module
{
public:
   double x{}, xd{}, xdd{}; // states

   Airy(size_t sim);

protected:
   void update();
};

Airy.cpp

#include "Airy.h"

Airy::Airy(size_t sim) : asc::Module(sim)
{
   addIntegrator(x, xd);
   addIntegrator(xd, xdd);

#define ascNS Airy
   ascVar(x)
}

void Airy::update()
{
   xdd = -t*x;
}

Initializing and Running

Main.cpp

Setup tracking, run the simulation, and generate output file.

#include "Airy.h"

int main()
{
   Airy system(0);
   system.name<Airy>("airy");
   system.x = 1.0;

   system.track("t");
   system.track("x");

   system.run(0.1, 10.0);

   system.outputTrack();

   return 0;
}

Generated File

airy.csv

t,airy x
0,1.000000
0.1,0.999833
0.2,0.998667
0.3,0.995504
0.4,0.989356
0.5,0.979253
continued...

Plotted Results:

Comparing Integration Methods:

By default Ascent runs a 4th order Runge Kutta integrator. The importance of using good integration methods is demonstrated below, showing how an Euler method at the same step size will diverge from the analytical solution.

Changing the integration method is simple, just add the code below:

#include "ascent/integrators/Euler.h"

int main()
{
   asc::integrator<asc::Euler>(0);
   ...