Setting up a Simulation

The following code is the boiler plate for setting up a PIC simulation in Aperture:

#include "framework/config.h"
#include "framework/environment.h"
#include "systems/data_exporter.h"
#include "systems/domain_comm.h"
#include "systems/field_solver_cartesian.h"
#include "systems/ptc_updater.h"

using namespace Aperture;

int main(int argc, char* argv[]) {
    typedef Config<2> Conf; // Specify that this is a 2D simulation
    // Initialize the simulation environment
    auto &env = sim_environment::instance(&argc, &argv);
    // Choose execution policy depending on compile options (GPU or CPU)
    using exec_policy = exec_policy_dynamic<Conf>;

    // Setting up domain decomposition
    domain_comm<Conf, exec_policy_dynamic> comm;
    // Setting up the simulation grid
    grid_t<Conf> grid(comm);
    // Add a particle pusher
    auto pusher = env.register_system<
        ptc_updater<Conf, exec_policy_dynamic, coord_policy_cartesian>>(grid,
        &comm);
    // Add a field solver
    auto solver = env.register_system<
        field_solver<Conf, exec_policy_dynamic, coord_policy_cartesian>>(grid,
        &comm);
    // Setup data output
    auto exporter = env.register_system<data_exporter<Conf,
        exec_policy_dynamic>>(grid, &comm);

    // Call the init() method of all systems
    env.init();

    // Prepare initial conditions
    ...

    // Enter the main simulation loop
    env.run();
}

The Config class contains compile-time configurations for the code, including the dimensionality (2 in the above example), data type for floating point numbers, particle pusher type, and indexing scheme.

The method register_system<T> constructs a :doc:/api/framework/system, puts it in the registry, and returns a pointer to it. When init() or run() is called, all the init() and run() methods of the registered systems are run in the order they are registered. In the above example, at every timestep, the code will first call the ptc_updater, then the field_solver, then the data_exporter.

There are two main ways to customize the problem setup, namely through the config file config.toml, or programmatically through coding initial or boundary conditions.

Config File

Every system has a number of parameters that can be customized through run time parameters. All parameters are read from a configuration file in the toml <https://github.com/toml-lang/toml>_ format. By default, the code will look for a file named config.toml in the same directory as the executable. A different config file can also be specified through a launch parameter:

$ ./aperture -c some/other/config/file.toml

Parameters are stored in an instance of :ref:params_store in the :ref:sim_environment class. One can also define all the required parameters programmatically:

sim_env().params().add("dt", 0.01);
sim_env().params().add("max_ptc_num", 100);

Since systems may use parameters in their constructors, one should add whatever needed parameters before initializing any systems.

Source Code

Some things need to be specified in the source code and require a recompile, e.g. non-trivial initial conditions. For example, one can assign an initial function to some field:

vector_field<Conf> *B0;  // Declare a pointer to the background B
env.get_data("B0", &B0); // Point it to the "B0" data component in the registry
double Bp = 100.0;       // Set a characteristic value for B
B0->set_value(0, [Bp](auto r, auto theta, auto phi) {
    return Bp / square(r);
}); // Set the 0th component (B_r) to a monopole field in spherical coordinates

Nontrivial boundary conditions can be more difficult to set up, especially time-dependent ones which requires the user to write a customized system. Please refer to The Aperture Framework for an explanation of how to write a custom system.