Source code for NetworkSim.simulation.simulator.parallel

__all__ = ["ParallelSimulator"]
__author__ = ["Hongyi Yang"]

import pandas as pd
from joblib import Parallel, delayed

from NetworkSim.architecture.base.network import Network
from NetworkSim.architecture.setup.model import Model
from NetworkSim.simulation.simulator.base import BaseSimulator


[docs]class ParallelSimulator: """ Parallel simulator class to enable multiple simulations to be run in parallel with minimum input parameters. All parameters of the simulation should be input as a list, and in one of the following fashions: - If the parameter is not specified, i.e. parameter value is ``None``, the parameter is set to the default value. - If one value of the parameter is specified, i.e. `parameter=[param_value]`, all simulations run on the same \ specified value. - Parameter values for all simulations are specified. The indices of the values in the list indicate the \ numbers of the simulators. Therefore, if two simulations with TT-FR and FT-TR are to be run, the parameters \ should be set to: `transmitter_type=['T', 'F'], receiver_type=['F', 'T']`. The number of simulators correspond to the length of the list in which all parameter values are specified. For \ example, if `average_data_rate=[10, 20, 30, 40, 50]`, 5 simulations will be created, and the simulator number \ corresponds to the index of the parameter values in the list input. The `summary()` function can be called \ after the simulation for an inspection of detailed parameters of each simulator used. Parameters ---------- until : float, optional The end time of the simulations. This should be specified if the simulation does not run on convergence. \ Default is ``None``. convergence : bool, optional Automatic convergence mode of simulation. Default is ``True``. sem_tr : float, optional The threshold standard error of mean (SEM) for convergence. Default is ``None``. bs : int, optional The batch size for SEM calculation during convergence. Default is ``None``. num_nodes : int, optional The total number of nodes in the network. Default value follows that in the `Network` class (``100``). transmitter_type : str, optional The type of transmitter, chosen from the following: - `tunable`, `t` or `T` Tunable transmitter (fixed receiver must be chosen). - `fixed`, `f`, or `F` Fixed transmitter (tunable receiver must be chosen). Default is ``tunable``. receiver_type : str, optional The type of receivers, chosen from the following: - `tunable`, `t` or `T` Tunable receiver (fixed transmitter must be chosen). - `fixed`, `f`, or `F` Fixed receiver (tunable transmitter must be chosen). Default is ``fixed``. traffic_generation_method : str, optional The source traffic generation method, chosen from the following: - `poisson` Poisson (biased negative exponential) distribution. - `pareto` Pareto distribution. Default is ``poisson``. max_data_rate : float, optional The maximum data rate at each node, in Gbit/s. Default is ``100``. average_data_rate : float, optional The expected average data rate at each node, in Gbit/s. Default is ``50``. bidirectional : bool, optional The type of network architecture, either bidirectional or unidirectional. \ Default is ``False``, which is unidirectional. seed : int, optional The seed used for source traffic generation. Default is ``1``. n_jobs : int, optional The number of processors used for parallel operations. Default is ``-1`` (all available processors). """ def __init__( self, until=None, convergence=None, sem_tr=None, bs=None, num_nodes=None, transmitter_type=None, receiver_type=None, traffic_generation_method=None, max_data_rate=None, average_data_rate=None, bidirectional=None, seed=None, n_jobs=-1 ): if until is None: until = [None] self.until = until if convergence is None: convergence = [None] self.convergence = convergence if sem_tr is None: sem_tr = [None] self.sem_tr = sem_tr if bs is None: bs = [None] self.bs = bs if num_nodes is None: num_nodes = [None] self.num_nodes = num_nodes if transmitter_type is None: transmitter_type = [None] self.transmitter_type = transmitter_type if receiver_type is None: receiver_type = [None] self.receiver_type = receiver_type if traffic_generation_method is None: traffic_generation_method = [None] self.traffic_generation_method = traffic_generation_method if max_data_rate is None: max_data_rate = [None] self.max_data_rate = max_data_rate if average_data_rate is None: average_data_rate = [None] self.average_data_rate = average_data_rate if bidirectional is None: bidirectional = [False] self.bidirectional = bidirectional if seed is None: seed = [1] self.seed = seed self.n_jobs = n_jobs self.simulator = None self.num_param = None self.env = None def _check_parameters(self): """ Function to check input parameters and standardise their format. """ # Check input consistency and type _length = [] _params = [ self.until, self.convergence, self.sem_tr, self.bs, self.num_nodes, self.transmitter_type, self.receiver_type, self.traffic_generation_method, self.max_data_rate, self.average_data_rate, self.bidirectional, self.seed ] for _param in _params: if isinstance(_param, list): if len(_param) > 1: _length.append(len(_param)) else: raise ValueError("Input parameter must be a list.") if _length.count(_length[0]) == len(_length): _length = _length[0] # Ensure consistency input list length if len(self.until) == 1: self.until = self.until * _length if len(self.convergence) == 1: self.convergence = self.convergence * _length if len(self.sem_tr) == 1: self.sem_tr = self.sem_tr * _length if len(self.bs) == 1: self.bs = self.bs * _length if len(self.num_nodes) == 1: self.num_nodes = self.num_nodes * _length if len(self.transmitter_type) == 1: self.transmitter_type = self.transmitter_type * _length if len(self.receiver_type) == 1: self.receiver_type = self.receiver_type * _length if len(self.traffic_generation_method) == 1: self.traffic_generation_method = self.traffic_generation_method * _length if len(self.max_data_rate) == 1: self.max_data_rate = self.max_data_rate * _length if len(self.average_data_rate) == 1: self.average_data_rate = self.average_data_rate * _length if len(self.bidirectional) == 1: self.bidirectional = self.bidirectional * _length if len(self.seed) == 1: self.seed = self.seed * _length else: raise ValueError("The numbers of input parameters do not match.") return _length def _initialise_simulator(self, simulator_id): """ Function to initialise individual simulators. Parameters ---------- simulator_id: int The ID of the simulator. """ if self.num_nodes[simulator_id] is not None: _network = Network(num_nodes=self.num_nodes[simulator_id]) _model = Model(network=_network, bidirectional=self.bidirectional[simulator_id]) else: _model = Model(bidirectional=self.bidirectional[simulator_id]) if self.max_data_rate[simulator_id] is not None: _model.constants['maximum_bit_rate'] = self.max_data_rate[simulator_id] if self.average_data_rate[simulator_id] is not None: _model.constants['average_bit_rate'] = self.average_data_rate[simulator_id] self.simulator[simulator_id] = BaseSimulator( until=self.until[simulator_id], convergence=self.convergence[simulator_id], sem_tr=self.sem_tr[simulator_id], bs=self.bs[simulator_id], model=_model, transmitter_type=self.transmitter_type[simulator_id], receiver_type=self.receiver_type[simulator_id], traffic_generation_method=self.traffic_generation_method[simulator_id], bidirectional=self.bidirectional[simulator_id], id=simulator_id, seed=self.seed[simulator_id] ) self.simulator[simulator_id].initialise() def _run_simulator(self, simulator_id): """ Function to run individual simulators. Parameters ---------- simulator_id : int The ID of the simulator. """ self.simulator[simulator_id].run()
[docs] def initialise(self): """ Initialisation of parallel simulations. """ self.num_param = self._check_parameters() self.simulator = [None] * self.num_param Parallel(n_jobs=self.n_jobs, require='sharedmem')(delayed( self._initialise_simulator)(i) for i in range(self.num_param))
[docs] def run(self): """ Run all simulations in parallel. """ Parallel(n_jobs=self.n_jobs, require='sharedmem')( delayed(self._run_simulator)(i) for i in range(self.num_param))
[docs] def info(self, simulation_id, info_type, component_type, component_id): """ Obtain simulation information from individual simulators. Parameters ---------- simulation_id : int The ID of the simulator. info_type : str The type of information requested, chosen from the following: - `control` or `c` Information on control ring. When `device_type == None`, this returns all packet transmission \ information on the control ring, otherwise it refers to control packets transmitted by a transmitter \ or control packets received by a receiver. `component_id` is not required in this case. - `data` or `d` Information on data ring. When `device_type == None`, this returns all packet transmission \ information on the data ring, otherwise it refers to data packets transmitted by a transmitter \ or data packets received by a receiver. An `component_id` must be specified in this case. component_type : str The type of component in the simulation, chosen from the following: - `ram` or `RAM` Transmitter RAM information, where data packets are generated. An `component_id` must be specified \ in this case, but `info_type` is not required. - `transmitter` or `t` Transmitter packet information. Both `component_id` and `info_type` must be specified. - `receiver` or `r` Receiver packet information. Both `component_id` and `info_type` must be specified. component_id : int The ID of the component of choice. Returns ------- """ return self.simulator[simulation_id].info( info_type=info_type, component_type=component_type, component_id=component_id )
[docs] def summary(self, simulator_id=None, summary_type=None): """ Obtain summaries of the simulations. Parameters ---------- simulator_id : int, optional The simulator ID of choice. Default is ``None``. If no ID is specified, a combined generic summary of \ the key parameters used in all simulators is produced. summary_type : str, optional The type of summary, chosen from the following: - `None` No `summary_type` input, and a generic summary is returned. - `latency` or `l` Latency summary, with latency information for all source-destination combinations. - `ram` or `RAM` Transmitter RAM data generation summary. - `transmitter` or `t` Transmitter summary. - `receiver` or `r` Receiver summary. This is an individual summary of the specified simulator, and therefore a `simulator_id` must be \ specified. """ if summary_type is None and simulator_id is None: _summary = [] for i in range(len(self.simulator)): _individual_summary = self.simulator[i].summary()['Value'].tolist() _individual_summary.insert(0, i) _summary.append(_individual_summary) _headers = [ 'BaseSimulator ID', 'Total Number of Nodes', 'Transmitter Type', 'Receiver Type', 'Source Traffic Generation Method', 'Direction of Transmission', 'Designed Average Data Rate (Gbit/s)', 'Designed Maximum Data Rate (Gbit/s)', 'Total Number of Data Packet Transmitted', 'Total Number of Transmission Error', 'Estimated Average Queueing Delay (ns)', 'Estimated Average Transfer Delay (ns)', 'Average Queueing Delay Latency (ns)', 'Average Transfer Delay Latency (ns)', 'Final Data Rate (Gbit/s)', 'Simulation Runtime (s)' ] return pd.DataFrame(_summary, columns=_headers) else: if simulator_id is None or not 0 <= simulator_id <= len(self.simulator): return ValueError("Please enter a valid simulator ID.") else: return self.simulator[simulator_id].summary(summary_type=summary_type)