Together, the hundreds of SimulationItem subclasses residing in the SKIRT/core
module (see Subprojects and modules) form the bulk of the SKIRT code. We use the term simulation item class to indicate a SimulationItem subclass and simulation item instance to indicate an instance of a SimulationItem subclass, i.e. a run-time object of that type. When the context allows, we use simulation items to loosely indicate both the classes and the instances.
Unsurprisingly, simulation items play a key role in SKIRT. They represent the configuration of a particular simulation, mapping class and property names to XML element and attribute names in the ski file (or to a question in the Q&A session), and they provide the code to actually perform the simulation and output the requested results.
SKIRT performs a simulation in three distinct phases:
A complete simulation is represented in SKIRT at run-time as a hierarchy of simulation item instances. The following diagram presents a simple example run-time simulation hierarchy (a connection starting with a diamond loosely means "A owns B"):
A run-time simulation hierarchy includes the following information:
The run-time simulation hierarchy mimics the contents of the corresponding ski file (see Structure of a ski file) with the exception of the ConsoleLog, FilePaths, and ParallelFactory instances, which are configured based on the command line arguments passed to SKIRT, and the Configuration instance, which automatically assembles some relevant aspects of the configuration (i.e. of the run-time hierarchy) for easy reference throughout the code.
Multiple run-time simulation hierarchies can co-exist and are independent of each other. There is no shared or global data, except when accessing truly global resources such as the console, which are protected by appropriate locking mechanisms.
In terms of implementation, all simulation item classes form a tree-like inheritance structure. The diagram below presents a tiny portion of the inheritance tree (for the full version, see SimulationItem):
The leaf nodes represent concrete simulation item classes. Instances of these classes can be part of a run-time simulation hierarchy. The non-leaf nodes represent abstract simulation item classes that can't be instantiated. Thus, simulation items form a compile-time class hierarchy through inheritance (with the SimulationItem class at the top), and a run-time instance hierarchy through aggregation (with an instance of a Simulation subclass at the top).
The basic interface inherited from SimulationItem facilitates common functionality for working with simulation items. For example, the SimulationItem::find() function allows locating a simulation item in a run-time simulation hierarchy simply by providing its class name. Also, the Simulation class cooperates with the SimulationItem interface to setup and run a complete simulation.
Furthermore, SimulationItem subclasses must provide appropriate metadata for use by the SMILE library (see The SMILE metadata subproject). The SMILE library then support various capabilities related to simulation items, including:
The SMILE code has no built-in knowledge about simulation item classes; it self-adjusts to the metadata provided in the respective class definitions. This means that:
To support all of this functionality, a SimulationItem subclass must comply with quite a number of requirements. The class obviously must directly or indirectly inherit the SimulationItem class. The class definition in the C++ header file must start with a section of SMILE metadata definitions describing any discoverable properties for instances of the class, i.e. properties that map to an attribute in the ski file. The metadata definitions use macros provided in the ItemInfo.hpp header file. These macros automatically define the corresponding data members and public getters, avoiding code repetition. In addition, the macros automatically define a default constructor (i.e. a constructor without arguments), which is the one invoked when a simulation hierarchy gets constructed. Because it is not possible to add custom code to the default constructor, all data members of the class must be initialized in the class declaration using the curly brace syntax.
Of course, the class must implement its custom functionality. If setup is required, the SimulationItem::setupSelfBefore() or SimulationItem::setupSelfAfter() function must be overridden. Other functions may be declared and defined as needed. Also, when deriving from a particular abstract SimulationItem subclass, such as Geometry, additional requirements may apply, as described for that abstract subclass.
Finally, a line of code must be added to the constructor of the SimulationItemRegistry class to register the new simulation item class to the SMILE library.
This development process is illustrated in the hands-on Adding a new geometry class [TUTORIAL]. Also, there are many existing simulation item classes that can serve as an example.
Further background and reference information is provided in the following topics: