Note

This static document was automatically created from the output of a jupyter notebook.

Execute and modify the notebook online here.

Recording Datasets#

Several use-cases may require for data generated by Hermes during measurement campaigns or simulation runtime to be stored within the filesystem for later use.

For this purpose, the Hermes API offers the record and replay functions of scenarios. During recording, all information processed by devices and operators is stored within HDF5 files. During replaying, the information can then be used as a basis of further evaluations, or the HDF5 file can be opened in other environments such as a Matlab workspace.

To demonstrate this functionality, let us first define a simplex link of an OFDM communication within two devices in an indoor office scenario without direct line of sight between transmitting and receiving device:

[2]:
import matplotlib.pyplot as plt
import numpy as np

from hermespy.channel import IndoorOfficeNoLineOfSight
from hermespy.core import dB
from hermespy.modem import SimplexLink, OFDMWaveform, OFDMLeastSquaresChannelEstimation, OFDMZeroForcingChannelEqualization, FrameElement, FrameResource, FrameSymbolSection, ElementType
from hermespy.simulation import SimulationScenario

# Define a new simulated scenario
scenario = SimulationScenario()
scenario.snr = dB(50)

# Consider two dedicated devices at a carrier frequency of 800 MHz
transmitting_device = scenario.new_device(carrier_frequency=800e6)
receiving_device = scenario.new_device(carrier_frequency=800e6)

# Specify the device's location within the scenario
transmitting_device.position = np.array([0., 0., 0.])
receiving_device.position = np.array([10., 10., 0.])

# Configure an indoor office channel on the device's link
scenario.set_channel(transmitting_device, receiving_device,
                     IndoorOfficeNoLineOfSight())
scenario.channel(transmitting_device, transmitting_device).active = False
scenario.channel(receiving_device, receiving_device).active = False

# The devices communicate via an OFDM waveform consisting of four identical symbols
waveform = OFDMWaveform(resources=[FrameResource(repetitions=50, elements=[FrameElement(ElementType.DATA, 11), FrameElement(ElementType.REFERENCE, 1)])],
                        structure=[FrameSymbolSection(4, [0])])
waveform.channel_equalization = OFDMZeroForcingChannelEqualization()
waveform.channel_estimation = OFDMLeastSquaresChannelEstimation()

modem = SimplexLink(transmitting_device, receiving_device, waveform=waveform)

Now, a single data drop can be simulated by calling the scenario’s drop routine. Internally, all device and scenario subroutines will be called to generate the transmitted waveforms, propagate over the channels and finally estimated the transmitted information at the receiver.

[3]:
drop = scenario.drop()

This drop now esentially contains a copy of the full scenario state consisting of all device’s transmitting and received information.

So, for a single drop, there are essentially two ways of accessing the generated data. Either via the operator and device interfaces:

[4]:
_ = modem.transmission.signal.plot(title='Transmitted OFDM Frame')
_ = modem.reception.signal.plot(title='Received OFDM Frame')
_ = modem.reception.symbols.plot_constellation(title='Constellation Before Equalization')
_ = modem.reception.equalized_symbols.plot_constellation(title='Constellation After Equalization')

plt.show()
../_images/notebooks_datasets_6_0.png
../_images/notebooks_datasets_6_1.png
../_images/notebooks_datasets_6_2.png
../_images/notebooks_datasets_6_3.png

Or, alternatively, over the generated drop object:

[5]:
# Make sure we're assuming the correct device indices
transmitter_idx = scenario.device_index(transmitting_device)
receiver_idx = scenario.device_index(receiving_device)

_ = drop.device_transmissions[transmitter_idx].operator_transmissions[0].signal.plot(title='Transmitted OFDM Frame')
_ = drop.device_receptions[receiver_idx].operator_receptions[0].signal.plot(title='Received OFDM Frame')
_ = drop.device_receptions[receiver_idx].operator_receptions[0].symbols.plot_constellation(title='Constellation Before Equalization')
_ = drop.device_receptions[receiver_idx].operator_receptions[0].equalized_symbols.plot_constellation(title='Constellation After Equalization')

plt.show()
../_images/notebooks_datasets_8_0.png
../_images/notebooks_datasets_8_1.png
../_images/notebooks_datasets_8_2.png
../_images/notebooks_datasets_8_3.png

Now that we demonstrated what kind of information a drop consists of, all we need to do is start recording drops into datasets:

[6]:
# Record a few drops into a dataset
num_drops = 10
scenario.record('dataset.h5', overwrite=True)
recorded_drops = [scenario.drop() for _ in range(num_drops)]

# Properly close the file
scenario.stop()

We are now able to recall the full scenario configuration, including devices, operators and their generated information from the filesystem.

Let’ just compare the first few received constellations for demonstration:

[7]:
# Deserialize a scenario from a dataset
recalled_scenario = SimulationScenario.Replay('dataset.h5')

# Recall a few drops
num_recalled_drops = 3
for n in range(num_recalled_drops):

    recorded_drop = recorded_drops[n]
    recalled_scenario.drop()

    # Plot the equalized constellation of recorded and replayed drops
    _ = recorded_drop.device_receptions[receiver_idx].operator_receptions[0].equalized_symbols.plot_constellation(title=f'Recorded Constellation #{n}')
    _ = recalled_scenario.devices[receiver_idx].receivers[0].reception.equalized_symbols.plot_constellation(title=f'Replayed Constellation #{n}')

# Properly close all streams, just in case
recalled_scenario.stop()

plt.show()
../_images/notebooks_datasets_12_0.png
../_images/notebooks_datasets_12_1.png
../_images/notebooks_datasets_12_2.png
../_images/notebooks_datasets_12_3.png
../_images/notebooks_datasets_12_4.png
../_images/notebooks_datasets_12_5.png

As we can clearly deduce, the recalled information is identical to the initially generated information.