Source code for macro_eeg_model.simulation.simulator

# standard imports
import sys

# external imports
import numpy as np
import colorednoise as cn
from tqdm import tqdm


[docs] class Simulator: """ The Simulator class is responsible for simulating EEG data using a vector autoregression (VAR) model. It generates synthetic EEG signals based on the provided lagged connectivity weights, noise characteristics, and other simulation parameters. Attributes ---------- _lag_connectivity_weights : numpy.ndarray The lagged connectivity weights matrix used for the VAR model. _sample_rate : int The sample rate of the simulation in Hz. _nr_lags : int The number of lags (p) in the VAR(p) model. _nr_nodes : int The number of nodes (channels) in the simulation. _t_secs : int The total time of the simulation in seconds. _t_burnit : int The burn-in time for the simulation in seconds. _noise_color : str The color of the noise to be used in the simulation ('white' or 'pink'). _std_noise : float The standard deviation of the noise to be used in the simulation. """
[docs] def __init__(self, lag_connectivity_weights, sample_rate, nr_lags, nr_nodes, t_secs, t_burnit, noise_color, std_noise): """ Initializes the Simulator with the provided parameters. Parameters ---------- lag_connectivity_weights : numpy.ndarray The lagged connectivity weights matrix used for the VAR model. sample_rate : int The sample rate of the simulation in Hz. nr_lags : int The number of lags (p) in the VAR(p) model. nr_nodes : int The number of nodes (channels) in the simulation. t_secs : int The total time of the simulation in seconds. t_burnit : int The burn-in time for the simulation in seconds. noise_color : str The color of the noise to be used in the simulation ('white' or 'pink'). std_noise : float The standard deviation of the noise to be used in the simulation. """ self._lag_connectivity_weights = lag_connectivity_weights self._sample_rate = sample_rate self._nr_lags = nr_lags self._nr_nodes = nr_nodes self._t_secs = t_secs self._t_burnit = t_burnit self._noise_color = noise_color self._std_noise = std_noise
[docs] def simulate(self, verbose=False): """ The simulation generates synthetic EEG signals by applying the VAR model to the provided lagged connectivity weights and adding noise. Parameters ---------- verbose : bool, optional If True, displays a progress bar during the simulation (default is False). Returns ------- numpy.ndarray A 2D array of shape (samples, nodes) containing the simulated EEG data. Raises ------ ValueError If an invalid noise color is provided. AssertionError If any of the input parameters are invalid (e.g., non-positive values for number of lags, time, or std). """ assert self._nr_lags > 0, f"Expected number of lags to be > 0, but got {self._nr_lags}" assert self._t_secs > 0, f"Expected simulation time to be > 0, but got {self._t_secs}" assert self._t_burnit >= 0, f"Expected simulation time to delete to be >= 0, but got {self._t_burnit}" assert self._std_noise >= 0, f"Expected noise std to be >= 0, but got {self._std_noise}" # approximates power of EEG cov = np.eye(self._nr_nodes) * (self._std_noise ** 2) # use Cholesky factorization to find R such that R'*R = C try: cov_cholesky = np.linalg.cholesky(cov).T except np.linalg.LinAlgError: raise ValueError('Covariance matrix is not positive definite. Try increasing std_noise.') # throw away the first t_burnit seconds of data to ensure model convergence nr_burnin = self._t_burnit * self._sample_rate # total number of samples required (including burn-in period) nr_samples = nr_burnin + self._t_secs * self._sample_rate # ---- NOISE match self._noise_color: case "white": white_noise = np.random.randn(nr_samples, self._nr_nodes) noise = white_noise * np.sqrt(self._sample_rate) case "pink": pink_noise = cn.powerlaw_psd_gaussian(1, (self._nr_nodes, nr_samples)).T noise = pink_noise case _: raise ValueError(f"Invalid noise color: {self._noise_color}. Expected 'white' or 'pink'.") random_noise = noise @ cov_cholesky # ---- SIMULATION simulated_eeg = np.zeros((self._nr_nodes, nr_samples)) loop_range = range(self._nr_lags, nr_samples) if verbose: loop_range = tqdm(loop_range, desc="Simulating model", ascii=True, leave=False, file=sys.stdout) for t in loop_range: # https://numpy.org/doc/stable/user/numpy-for-matlab-users.html# ar_input = ( self._lag_connectivity_weights @ simulated_eeg[:, np.arange(t, t - self._nr_lags, -1)].reshape( self._nr_lags * self._nr_nodes, order='F' ) ) simulated_eeg[:, t] = ar_input + random_noise[t, :] sys.stdout.flush() simulation_data = simulated_eeg[:, nr_burnin:].T return simulation_data