The Diagnostic Manager

Data output in Schnek is handled by Diagnostic classes. These classes inherit from the Block class and thus can be controlled by the simulation’s setup file. The typical behaviour of Diagnostic classes is to write some data to files at specified intervals. In general the Diagnostic classes do not have to be called directly as this is handled by the DiagnosticManager. DiagnosticManager is a singleton class that defines a number of methods used to control diagnostic output. It stores all instances of the Diagnostic class and keeps track of the current time.

class DiagnosticManager
{
  public: 
    static const DiagnosticManager& instance();
    
    void setTimeCounter(int *timecounter);
    void setPhysicalTime(double *physicalTime);
    
    void execute();

    double adjustDeltaT(double deltaT);
    
    // the following functions are not usually called directly
    void addIntervalDiagnostic(IntervalDiagnostic*);
    void addDeltaTimeDiagnostic(DeltaTimeDiagnostic*);
};

Being a singleton you can not create instances of the DiagnosticManager directly. Instead you need to access the singleton instance through the static instance() method.

The Diagnostic class has two sub-classes that handle two different types of diagnotic. IntervalDiagnostic will write out data after a given number of simulation steps, whereas DeltaTimeDiagnostic will write out data after a specified physical time. The functions addIntervalDiagnostic() and addDeltaTimeDiagnostic() are used to add diagnostic instances to the manager. These functions are not usually called directly. Every instance of the IntervalDiagnostic or DeltaTimeDiagnostic will automatically call the relevant function to add itself to the DiagnosticManager.

The simulation code should, however, supply a reference to either a time counter or a physical time through the functions setTimeCounter() and setPhysicalTime(). The argument is a pointer to the global simulation time step or the global physical simulation time. The pointer must be valid throughout the duration of the simulation. Usually these functions should be called once during the startup of the simulation. Note that only one of the two functions must be set if the simulation defines only IntervalDiagnostic or only DeltaTimeDiagnostic classes. On the other hand it is allowed to mix the two types in a single simulation.

While the simulation is running the global time step counter or the physical simulation time (or both) should be during after each simulation step. After carrying out the numerical simulation step, a call to execute() will iterate through all registered Diagnostic output objects and check if any data needs to be written. A skeleton of a simulation loop is outlined below.

int timeStep = 0;
double time = 0.0;

void run() {
  DiagnosticManager::instance().setTimeCounter(&timeStep);
  DiagnosticManager::instance().setPhysicalTime(&time);
  
  while (time <= simulationEndTime) {
    doSimulationStep();
    
    ++timeStep;
    time += dt;
    
    DiagnosticManager::instance().execute();
  }
}

Another function that can be useful when using the physical simulation time is adjustDeltaT(). This function is intended to be called before the numerical simulation step. Consider the case in which the natural simulation time step dt is 0.2 but one of the diagnostic outputs is requested through the setup file after a physical time interval of 0.5. When the simulation time is 0.4 the next time step would take it to 0.6, way beyond the desired output time of 0.5. The adjustDeltaT() calculates the next simulation time step, based on the base time step and the time of the next diagnotic outputs. The code skeleton below illustrates how it can be used.

int timeStep = 0;
double time = 0.0;
double dt = 0.2;

void run() {
  DiagnosticManager::instance().setTimeCounter(&timeStep);
  DiagnosticManager::instance().setPhysicalTime(&time);
  
  while (time <= simulationEndTime) {
    double dtAdjust = DiagnosticManager::instance().adjustDeltaT(dt);
    
    doSimulationStep(dtAdjust);
    
    ++timeStep;
    time += dtAdjust;
    
    DiagnosticManager::instance().execute();
  }
}

A serious warning comes with this feature. Many numerical schemes use staggered time steps, such as leap-frog schemes. Changing the time step during the simulation can reduce the accuracy of the numerical scheme drastically. It is therefore advised to only use the adjustDeltaT() function if you know that your numerical scheme is not affected by this.