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