Simulation

The simulation module provides the basis for all HermesPy simulations, including noise and hardware modeling.

It is design around the Ray framework for parallel and distributed computing, enabling the efficient parallelization of physical layer Monte Carlo simulations scaling to multicore-CPUs, computing clusters and cloud computing environments. Every Monte-Carlo physical layer simulation campaign is composed of a set of parallel numeric physical layer simulations, each of which is individually parameterized.

%%{init: {"flowchart":{"useMaxWidth": false}}}%% flowchart TD pla[Simulated Physical Layer] plb[Simulated Physical Layer] c{Simulation} plc[Simulated Physical Layer] pla --> |Evaluations| c c --> |Parameters| pla plb --> |Evaluations| c c --> |Parameters| plb plc --> |Evaluations| c c --> |Parameters| plc

During execution, a central simulation controller will distribute parameter combinations selected from the simulation parameter grid to the individual physical layer simulation instances and collect the resulting performance evaluations. Each of the physical layer simulations is executed in a separate Python thread and distributed to the available CPU / GPU and memory resources by Ray.

The physical layer in turn, at its core, is composed of simulated devices and reciprocal channels interconnecting the physical devices.

%%{init: {"flowchart":{"useMaxWidth": false, "curve": "linear"}}}%% graph LR subgraph phys [Simulated Physical Layer] direction LR dai[SimulatedDevice A] dbi[SimulatedDevice B] caa{{Channel A,A}} cab{{Channel A,B}} cba{{Channel B,A}} cbb{{Channel B,B}} dao[SimulatedDevice A] dbo[SimulatedDevice B] end dai -.-> caa -.-> dao dai -.-> cab -.-> dbo dbi -.-> cbb -.-> dbo dbi -.-> cba -.-> dao click dai "/api/simulation.simulated_device.SimulatedDevice.html" "Simulated Device" click dbi "/api/simulation.simulated_device.SimulatedDevice.html" "Simulated Device" click dao "/api/simulation.simulated_device.SimulatedDevice.html" "Simulated Device" click dbo "/api/simulation.simulated_device.SimulatedDevice.html" "Simulated Device" click caa "/api/channel.channel.Channel.html" "Channel Model" click cab "/api/channel.channel.Channel.html" "Channel Model" click cba "/api/channel.channel.Channel.html" "Channel Model" click cbb "/api/channel.channel.Channel.html" "Channel Model"

Simulated devices represent any physical entity capabable of transmitting or receiving electromagnetic waveforms. This can be on the one hand communication devices such base-stations, smartphones, wifi routers, laptops or other Internet-of-things devices. On the other hand, they may be sensing devices such as automotive FMCW radars and bistatic radar transmitters and receivers. In general, devices can be aribtrarily positioned and oriented in space and transmit and/or receive arbitrary electromagnetic waveforms at arbitrary carrier frequencies.

The physical properties of devices’ front-end hardware can be specified in detail to allow for a more realistic simulation of the hardware effects on the transmitted and received signals. This includes modeling analog to digital conversion (ADC) and digital to analog conversion (DAC) introducing quantization noise and non-linearities, oscillator effects such as in-phase/quadrature imbalance (I/Q) and phase noise (PN), amplification non-linearities in the power amplifier (PA) and low-noise amplifier (LNA), mutual coupling between individual antennas in antenna arrays (MC), polarimetric radiation patterns of individual antennas in antenna arrays (ANT), and transmit-receive isolation between transmitting and receiving antennas in antenna arrays of duplex devices (ISO).

graph LR subgraph rftx [RF Tx Chain] direction LR dac[DAC] --> iqtx[I/Q] --> pntx[PN] --> pa[PA] end pa --- txsplit(( ))--> mctx[MC] --> anttx[ANT] --> chantx{{Tx}} subgraph rfrx [RF Rx Chain] direction LR lna[LNA] --> pnrx[PN] --> iqrx[I/Q] --> adc[ADC] end chanrx{{Rx}} --> antrx[ANT] --> mcrx[MC] --- rxmerge((+)) --> lna txsplit --> iso[ISO] --- rxmerge

Considering a single simulation iteration, within each simulated device, a digital base-band signal model to be transmitted is generated and sequentially passed through the radio-frequency transmission chain (RF Tx) models. The emerging analog signal is then duplicated, with one copy being passed through the mutual coupling and antenna models and the other copy being passed throug the transmit-receive isolation model. The signal copy considering mutual coupling and antenna effects is then propagated over all channel models linking the specific device to all other devices in the simulation. The received signals, after propgation over the channel models, are then once again passed through the antenna and mutual coupling models. Before considering the effects of the radio-frequency reception chain (RF Rx) models, the received signals are superimposed with the signal copy considering transmit-receive isolation. Finally, the received signal is passed through the RF Rx models and the resulting digital base-band signal is processed by the configured receive digital signal processing.

Note

The order of hardware-impairment models is currently fixed and cannot be changed. This system is under active development and will be expanded in the future to enable arbitrary ordering of hardware-impairment models and thus more accurate representations of custom front-ends.

Configuring the simulation exactly as the above graphs show is as simple as:

 1# Alias the ideal channel as Channel
 2Channel = IdealChannel
 3
 4# Initialize a new simulation
 5simulation = Simulation()
 6
 7# Limit the number of actors, i.e. parallel physical layers, to 3
 8simulation.num_actors = 3
 9
10# Add two devices to the simulation
11device_alpha = simulation.new_device()
12device_beta = simulation.new_device()
13
14# Configure the three unique channels
15alpha_alpha_channel = Channel()
16beta_beta_channel = Channel()
17alpha_beta_channel = Channel()
18
19simulation.set_channel(device_alpha, device_alpha, alpha_alpha_channel)
20simulation.set_channel(device_beta, device_beta, beta_beta_channel)
21simulation.set_channel(device_alpha, device_beta, alpha_beta_channel)

In practice, limiting the number of parallel simulations by setting the num_actors property is usually not required, as Ray will automatically scale the number of parallel simulations. However, sometimes memory constraints may require setting a lower value.

Typical parameters to be varied in a Monte-Carlo simulation are the receive signal-to-noise ratio and the carrier frequency of the exchanged signals. For the specific values of an SNR between 0 and 20 dB and carrier frequencies of 1, 10 and 100 GHz, the overall simulation parameter grid may look as follows:

Carrier-Frequency

SNR

\(1~\mathrm{GHz}\)

\(10~\mathrm{GHz}\)

\(100~\mathrm{GHz}\)

\(0~\mathrm{dB}\)

\((\ 0~\mathrm{dB}, 1~\mathrm{GHz})\)

\((\ 0~\mathrm{dB}, 10~\mathrm{GHz})\)

\((\ 0~\mathrm{dB}, 100~\mathrm{GHz})\)

\(10~\mathrm{dB}\)

\((10~\mathrm{dB}, 1~\mathrm{GHz})\)

\((10~\mathrm{dB}, 10~\mathrm{GHz})\)

\((10~\mathrm{dB}, 100~\mathrm{GHz})\)

\(20~\mathrm{dB}\)

\((20~\mathrm{dB}, 1~\mathrm{GHz})\)

\((20~\mathrm{dB}, 10~\mathrm{GHz})\)

\((20~\mathrm{dB}, 100~\mathrm{GHz})\)

The simulation class provides a convenient interface to sweep over virtually any class property of any class contained within the simulation. In the above example, the carrier frequency is configured by setting the carrier_frequency property of the simulated devices. Since the SNR is a frequently used parameter, it has a shorthand and can be globally set for all devices within the simulation without the need to specify the device class:

 1
 2# Configure the three unique channels
 3alpha_alpha_channel = Channel()
 4beta_beta_channel = Channel()
 5alpha_beta_channel = Channel()
 6
 7simulation.set_channel(device_alpha, device_alpha, alpha_alpha_channel)
 8simulation.set_channel(device_beta, device_beta, beta_beta_channel)
 9simulation.set_channel(device_alpha, device_beta, alpha_beta_channel)
10
11# Sweep over the SNR from 0 to 20 dB in steps of 10 dB
12simulation.new_dimension("noise_level", dB(0, 10, 20), device_beta)
13
14# Sweep over the carrier frequency from 1 GHz to 100 GHz in steps of 10 GHz
15simulation.new_dimension("carrier_frequency", (1e9, 1e10, 1e11), device_alpha, device_beta)

Now, by default, devices won’t transmit or receive any signals. For the sake of simplicity in this example, we will configure the devices to transmit and receive \(100\) samples of a random complex signal with an amplitude of \(1\) and a bandwith of \(100~\mathrm{MHz}\).

 1# Make both devices transmit 100 samples at 100 MHz
 2ns, fs = 100, 1e8
 3transmitted_signal = Signal.Create(np.exp(2j * np.random.uniform(0, np.pi, (1, ns))), fs)
 4alpha_transmitter = SignalTransmitter(transmitted_signal)
 5beta_transmitter = SignalTransmitter(transmitted_signal)
 6alpha_receiver = SignalReceiver(ns, fs, 1.0)
 7beta_receiver = SignalReceiver(ns, fs, 1.0)
 8
 9device_alpha.transmitters.add(alpha_transmitter)
10device_beta.transmitters.add(beta_transmitter)
11device_alpha.receivers.add(alpha_receiver)
12device_beta.receivers.add(beta_receiver)

Even though the simulation is now fully configured in terms of its basic physical layer description and the parameter grid, no performance evaluations will be generated when executing the simulation. Instead, each performance indicator to be evaluated must be explicitly configured. Multiple modules of HermesPy provide implementations of performance indicator evaluators for performance indicators relevant to the respective module’s topic. In this example, one of the most basic performance indicators is the signal power received by each device:

1# Add an evaluator estimating the received power to the simulation
2simulation.add_evaluator(ReceivedPowerEvaluator(alpha_receiver))
3simulation.add_evaluator(ReceivedPowerEvaluator(beta_receiver))

The simulation can be executed by calling the run method. All configured performance indicators will be evaluated for each parameter combination and the results returned in a MonteCarloResult. From there, the result can be printed to the console, plotted, or saved to the drive.

1# Run the simulation
2result = simulation.run()
3
4# Print the result and plot graphs
5result.print()
6result.plot()
7
8# Save the result to a file
9result.save_to_matlab("simulation.mat")