## 2 Materials and Methods
### Scheduling Algorithm  
The scheduling paradigm employed by mosaik can be interpreted as a flexible discrete-time simulation. One of its basic concepts is the so-called data flow graph, a directed graph that indicates the data dependencies between the simulators employed in the co-simulation. The graph is established automatically at the beginning of a co-simulation process. Its structure is based on the connections between simulators that are defined by a user via the scenario-API in a scenario script. For each simulator \(S\), the graph indicates which other simulators provide \(S\) with data (predecessors) and which require data from \(S\) (successors). After the graph is established, an individual simulation process is started for each employed simulator. The structure of such a process is depicted in Figure 2.  
The process for a given simulator is structured in steps that indicate its temporal progression. The step size is measured in integers to guarantee comparability between the timing of different simulators. The simulation process iterates for each simulator, each time performing a step, until the end of the co-simulation is reached. The data exchange between simulators is conducted asynchronously to improve co-simulation performance. At the beginning of each simulation process iteration, it is checked whether a new simulation step is required. If the end of the co-simulation is reached or all predecessors have stopped already, the simulator in question is stopped. Right before stopping, the simulator triggers its (unstopped) predecessors to allow them to check whether they can also stop. Unstopped simulators continue with their simulation process and check whether a simulation step is currently expected from them. This is true if the simulator in questions possesses no successors or if any successor is currently waiting for data input. A simulator that requires a next step then checks whether it needs input data from its predecessors and waits if that is the case. In other words, waiting for input data can propagate across different simulation processes until a simulator with no open dependencies provides the initially needed data. The waiting between simulation processes is managed via the discrete event simulation (DES) framework _Simpy_ ([[https://simpy.readthedocs.io/en/latest/](https://simpy.readthedocs.io/en/latest/)]([https://simpy.readthedocs.io/en/latest/](https://simpy.readthedocs.io/en/latest/))). This framework enables the creation of wait events to pause processes and trigger them again if certain conditions are resolved (typically data availability). This allows for efficient management of the timing of asynchronous simulation processes. The actual data exchange between a simulator and mosaik is divided into three stages. First, the input data required by the simulator is read from mosaik's internal buffer and put into the simulator's input buffer. The data requirement is specified via the data flow graph. Furthermore,  
Figure 2: Simulation process for an individual simulator. the data in the mosaik buffer is associated with timestamps to make sure that only valid data is provided. After the data provision, the actual simulation step for the simulator is performed by calling the appropriate function of the simulator interface. All data within the simulator's input buffer as well as its current step time is provided as input. Stepping triggers calculations of the simulator and advances it in time. The call is conducted asynchronously so that it does not needlessly block the overall co-simulation. As a response to the call, the simulator provides its next time step. This way, simulators can adjust their temporal resolution during execution and are not bound to one fixed step size. After the step execution, finally, the simulator receives an asynchronous request for all output data its successors expect. The data is stored in the mosaik buffer. This allows for the reuse of old data without the need to request it again from the simulator.