Architecture Overview
The general structure of a simulation is sketched below. It starts with an instance of the Instrument class, which represents the user interface that receives the simulation parameters. The interface is monolithic (for historic reasons) but all of the actual work is delegated to modular components which each serve a single task.
As a design principle, we access functionality through abstract interfaces, each of which can have different implementations. As a concrete example, there is an interface that provides orbital separation as function of time. One implementation just returns constant data, another implementation interpolates sampled data. The code parts that require separation only see the interface but don’t need to know how it is provided.
The Instrument class instantiates implementation of the required interfaces according to the user parameters. Most interfaces provide functions of time, in detail
Gravitational wave impact on light travel times
Orbital separation
Glitch signals for various injection points
Frequency plan
Other interfaces provide signal processing services
Delay operators
Antialiasing filters and downsampling
Further, the instrument class instantiates two concrete classes which provide
Definitions of each instrumental noise
All remaining simulation parameters not already used to set up the components above
All those ingredients are then passed into the core part of the simulation, implemented by a class ModelConstellation. The purpose of this class is to create a description how the time series that define the instrument’s operation are related to each other. It does not compute anything yet, but sets up a network of tasks. For example, this model could declare that some time series A can be obtained by delaying another time series B by an amount given by a third time series C.
The way this modeling is implemented makes use of the streams package for chunked processing developed specifically for this project. The central concept is called a stream. A stream represents an infinite time series which can be evaluated on a finite index range. Streams can depend on other streams, acting as nodes in the dependency graph, or represent a source with no input. Any stream can act as output. We defer a detailed description of the streams framework. For now, the important aspect is that the ModelConstellation provides a model of the instrument as a collection of interconnected streams, in a class called StreamBundle.
The final part of the simulation is to evaluate the streams and store the results somewhere. This is done by a component of the streams framework called the scheduler. The scheduler divides the requested time interval into parts that fit into memory and evaluates each required stream. Each chunk of the requested output streams is send to a storage interface.
There are different implementations of the interface for data storage. One implementation saves the incoming data directly to a HDF5 file. Another implementation saves the data to memory, as a collection of ordinary numpy arrays. The latter is used for work with short simulations that fit into memory. In contrast, the memory footprint for saving to file has a constant bound independent of the simulation length.
The components for orbits, GW response, glitches, and frequency plan are not based on streams, but use plain numpy. However, they are designed for the use case where the stream-based code will successively evaluate the functions they provide on short time intervals. For example, when using GW data from file, only the portion needed for a given query will be read to memory.
flowchart LR
instr[Instrument]
subgraph numpy_based [Numpy-based]
gw_source[GWSource]
orbiting[OrbitSource]
fplan_source[FreqPlanSource]
glitches[GlitchSource]
end
subgraph config[Configuration]
model_cfg[ModelConstellationCfg]
instru_noises[InstruNoises]
end
subgraph stream_based [Stream-based]
delays[InstruDelays]
filter[filter]
instru_model[ModelConstellation]
bundle[StreamBundle]
scheduler[scheduler]
storage[DataStorage]
end
subgraph output[Output]
memory[SimResultsNumpy]
hdf5[(HDF5 Files)]
end
instr --> gw_source
instr --> orbiting
instr --> fplan_source
instr --> glitches
instr --> instru_noises
instr --> delays
instr --> filter
instr --> model_cfg
gw_source --> instru_model
orbiting --> instru_model
fplan_source --> instru_model
glitches --> instru_model
instru_noises --> instru_model
model_cfg --> instru_model
delays --> instru_model
filter --> instru_model
instru_model --> bundle
bundle --> scheduler
scheduler --> storage
storage --> hdf5
storage --> memory