RAN Digital Twin#

The following sections describe how to run simulations in three different modes:

  • Mode 1 - Electromagnetic Only (EM): computes radio propagation using ray tracing

  • Mode 2 - Radio Access Network (RAN): runs AERIAL 5G RAN protocols in addition to the EM simulation

  • Mode 3 - Machine Learning Examples (ML):

    • ML Example 1: EM + ML Channel Prediction

    • ML Example 2: EM + RAN + ML Channel Estimation

Simulation Mode 1 - EM Simulation#

The EM simulation mode simulates the electromagnetic propagation between transmitters and receivers in a virtual 3D environment. It generate the wireless channel between them, but does not execute any component of the RAN. As such, it can be used to generate large quantities of synthetic data which can be accessed through SQL for further processing.

The EM mode, and all other modes, require some common steps:

  1. Attaching worker - a worker is a node that performs computations (raytracing, channel emulation, antenna modelling, as well as RAN and ML). The worker communicates with the UI via a Nucleus server.

  2. Creating and configuring antenna panels

  3. Deploying network components (RUs and DUs)

  4. Deploying UEs and configuring their mobility patterns or respawn settings

  5. Creating antenna panels for UEs and RUs to use

  6. Configuring raytracing simulations and executing them.

These steps are described in more detail below.

Attaching a worker to the UI#

As discussed in the previous sections, the Aerial Omniverse Digital Twin consists of five subcomponents:

  • the graphical user interface

  • Nucleus

  • ClickHouse

  • the scene importer

  • and the RAN digital twin,

where the Nucleus server is the element that allows all the others to interact with one another.

Our entry point to running simulations is the graphical user interface. After opening the graphical interface, we can navigate to the Configuration tab to attach to an instance of the RAN digital twin, here referred to also as a worker.

Once in the Configuration tab, we shall enter the DB host, DB port for the ClickHouse server and press the Connect button. If the server is reached successfully, the indicator next to the Connect button will go from red to green. Continuing, we can then add a DB name, and optionally a DB author. DB notes can be left empty or can be used to describe key characteristics of the simulation which can help us to retrieve at a later point. We can disconnect from the DB at any time by clicking the Disconnect button.

Next, we can enter the Nucleus server URL, e.g. omniverse://<Nucleus IP or hostname>, and the desired live Session Name. The Broadcast Channel Name is an optional parameter to additionally isolate multiple workers running on the same node. By default, Broadcast Channel Name is simply broadcast. Finally, we can specify the URLs of the Assets installed on the selected Nucleus server during installation.

After these steps, we are ready to click on the Attach worker button from the toolbar, which is the icon represented by a set of gears. If there is a problem with the installation and the graphical user interface is not able to communicate with the worker, an error window will pop up.

../_images/attach_worker_error_window.png

Differently, if the worker attaches successfully, the gear icon will turn green as shown below. To detach the worker, we can click the gear icon again and confirm we want to detach the work. The icon will turn gray again.

../_images/mobi_worker_locked.png ../_images/mobi_worker_unlocked.png

It is worth mentioning that it is not necessary to explicitly connect to the database each time since attaching the worker will also connect to the database. Of course, the DB host and DB port need to be valid for this to happen.

After attaching the worker, we are ready to open a scene. We can do so by going to File > Open and selecting a scene, e.g. tokyo.usd, from the Nucleus server. Alternatively, we can use the Content tab and double click on the file we want to open.

After the UI and the worker both open the scene, we will see a 3D map in the viewport, and the Live session icon in the top right will turn green, indicating that the live session is active.

Adding antenna panels#

Next, we need to create the antenna panels that the RUs and UEs will use.

We can create a new antenna array by right clicking on the Stage widget and selecting the Aerial > Create Panel entry from the context menu. The new panel can be found in the Stage widget under the Panels entry. By selecting the new panel, we can inspect its properties and change using the Antenna Elements tab and the Property widget as illustrated in the figure below.

../_images/antenna_panel_property_widget.png

If needed, under the Antenna Elements tab, for half-wave dipoles, it is possible to calculate the active element pattern for each antenna element in the array using the Calculate AEP. This takes into account the mutual coupling effects that each antenna elements experiences due to the presence of the others.

Refer to the Graphical User Interface section of this guide for more details on how to deploy custom antenna patterns.

Deploying RUs#

To deploy new radio units (RUs), it is sufficient to right click on the map with the mouse and select Aerial > Deploy RU. This will create a movable asset which follows the mouse. Once the location of the RU is found, we can click to confirm the position of the RU. The RU can be later moved by selecting it, right clicking on it and using Aerial > Move RU from the context menu.

After a given RU is in the intended position, its attributes can be modified using the property widget. Most importantly we need to associate a Panel Type if the field is empty.

Deploying DUs#

To deploy a DU, we can right click on a map area and select Aerial > Deploy DU. We can manually or automatically associate a DU to a RU. More details are in the Graphical User Interface section of this guide.

Deploying UEs#

The UEs can be deployed in two ways - procedurally or manually. To deploy manually, we can navigate to the viewport and right click on the position where we would like the UE to be located. Selecting Aerial > Deploy UE from the context menu will create a capsule in the desired location. The corresponding entry in the UEs group of the stage widget will have the Manually Created flag active.

For a manually created UE, we can also specify its mobility path by clicking on the Edit Waypoints button in the UE property widget. Then, in the viewport we can draw a polyline defining the intended trajectory of the UE across the map.

../_images/ue_edit_waypoints_property_widget.png ../_images/drawing_ue_manual_waypoints.png

Additionally, the following properties can be customized for each manually drawn waypoint:

  • Speed: Defined in meters per second.

  • Azimuth Angle Offset: Measured in degrees.

  • Pause Duration: The length of time, in seconds, the UE pauses at the waypoint.

../_images/ue_waypoint_attribute_widget.png

This approach is typically sufficient to simulate small scenarios, where the number of UEs is limited. For larger populations of UEs, we can procedurally generate them by changing the parameter Number of Procedural UEs in the Scenario entry of the Stage widget. Another parameter Percentage of Indoor Procedural UEs controls the percentage of procedurally generated UEs that are placed indoor. Note manual UEs cannot be deployed indoor. Pressing the Generate UEs button in the toolbar, when the worker is attached, will procedurally create enough UEs, so that the number of procedural UEs matches the one specified in Scenario. If Number of Procedural UEs is lower than the existing number of procedural UEs, the additional procedural UEs will be deleted to match the setting.

We can constrain where the procedural UEs are generated by creating a Spawn Zone, i.e., by right clicking in the viewport and selecting Aerial > Deploy Spawn Zone. This will create the bounding box show in the figure.

../_images/spawn_zone_bounding_box.png

The size and position of the bounding box can be adjusted using the Move, Rotate, and Scale buttons in the toolbar. More in detail, after selecting one of such actions, we can drag the red/blue/green arrows and rectangles show in the figure to execute the desired transformation.

../_images/scale_rotate_move_widget.png

In all cases, it is important that the bounding box intersects with the ground of the stage. Otherwise, the procedural UEs will not be dropped in the spawn zone. For this reason, we might want to modify the spawn zone bounding box from the top view, instead of perspective view (the view can be changed from the Camera view widget at the top of the viewport).

../_images/camera_view.png

If there is no spawn zone or if the spawn zone bounding box is too small, then procedural UEs will be dropped in a random position in the scene. Procedural and manual UEs can be mixed for a given deployment, and manual UEs are not moved at every press of the Generate UEs button.

Changing the scattering properties of the environment#

The proper association of materials to the scene geometry plays a key role in producing a realistic representation of the radio environment. Currently, materials can be assigned to each building and to the terrain as a whole. To do so,

  • we can select the building we want to edit using either the stage view or the viewport and then proceed to alter the field Building under Building Materials for maps that do not contain segmented buildings data and the fields

    • Building Installation definition

    • Wall Surface

    • Roof Surface

    • Ground Surface

    under Building Materials in the property widget;

  • similarly, for the terrain, we can select the ground_plane asset in the stage view and then modify the Ground Plane Material field from the property widget.

For the buildings, it is also possible to batch assign a given material to the whole map by selecting World/buildings in the stage widget.

With a similar procedure and interface, we can also assign two other important properties:

  • Enable RF: this flag indicates whether the mesh or meshes representing

    • one building,

    • all of the buildings

    • or the terrain

    can interact with the electromagnetic field. If this flag is not enabled the electromagnetic field will not be able to interact with the geometry of the selected asset;

  • Enable Diffusion: this option in turn specifies whether the mesh or meshes - again representing

    • one building

    • all of the buildings

    • or the terrain

    can interact with the electromagnetic field in a diffuse fashion, i.e., whether the surface of such meshes can produce non-specular reflections.

Running simulations#

Before running the simulation, it is important to check that all of the parameters in the Scenario property widget are aligned with our intentions and that Enable Training and Simulate RAN are both unchecked.

As mentioned in the previous section, the duration and the sampling period of the simulation is determined by the Simulation Mode in Scenario.

  • Simulation Mode: Duration requires to set

    • Batches,

    • Duration,

    • Interval

  • Simulation Mode: Slots instead requires

    • Batches,

    • Slots Per Batch,

    • Samples Per Slot.

Refer to the Graphical User Interface section describing the Scenario stage widget for more details on these and other parameters.

Now, we can generate the UEs and the trajectories that they will follow during the simulation using the Generate UEs button. The trajectories appear in the viewport widget as white polylines on the ground plane, and in the Stage widget as entries of the runtime scope. Once the UEs and their trajectories are available, we can start the simulation by pressing the Start UE mobility button in the toolbar.

While running, the simulation can be paused and stopped using the Pause UE mobility and the Stop UE mobility buttons of the toolbar. While the simulation is paused, the Generate UEs button can be pressed but it will not generate a new set of trajectories. In order to so, the simulation will have to be stopped using the Stop UE mobility button. The progress of the simulation is shown in the progress bar.

../_images/sim_progress_bar.png

Viewing simulation results#

When the simulation is complete, we can press the Play button on the toolbar or move the blue indicator in the Timeline widget to a specific frame of interest. To stop the replay, we can click the Stop button.

The visualization of the rays can be turned on or off for each RU-UE pair by selecting the RU and UE ahead of the simulation and enabling the Show Raypaths attributes in the Property widget.

If a given RU or UE is not selected before the simulation was launched, and we are interested in seeing the rays of that RU-UE pair, we can use the Refresh telemetry button.

Radio environment#

The radio environment results stored in the database are for the RU to UE direction, i.e., for downlink. Specifically, if we take

  • the total transmitted power \(P^{\left(RU\right)}\) at RU,

  • the number of polarizations used at RU per transmitting antenna site \(N^{\left(RU\right)}_{pol.}\)

  • the number of horizontal antenna elements used at the RU \(N^{\left(RU\right)}_{hor.}\)

  • the number of vertical antenna elements used at the RU \(N^{\left(RU\right)}_{vert.}\)

  • the number of FFT points \(n\)

  • the channel frequency response per link \(\mathbf{H}_{i,j}^{\left(UE\right)}\left( k \right)\) observed at the UE for a given subcarrier \(k\), across the link from the \(i\)-transmitter antenna to the \(j\)-th receiver antenna

  • the channel frequency response per link \(\mathbf{H}_{i,j}^{\left(ch\right)}\left( k \right)\) observed at the UE for a given subcarrier \(k\), across the link from the \(i\)-transmitter antenna to the \(j\)-th receiver antenna when each subcarrier is allocated unitary power at transmission

the results are such that

\( \left<\mathbf{H}_{i,j}^{\left(UE\right)}, \mathbf{H}_{i,j}^{\left(UE\right)} \right> = \dfrac{P^{\left(RU\right)}}{n \cdot N^{\left(RU\right)}_{pol.} \cdot N^{\left(RU\right)}_{hor.} \cdot N^{\left(RU\right)}_{vert.}} \left<\mathbf{H}_{i,j}^{\left(ch\right)}, \mathbf{H}_{i,j}^{\left(ch\right)} \right>. \) The set of \(\left\{\mathbf{H}_{i,j}^{\left(UE\right)}\left( k \right)\right\}_{i,j,k}\) is stored in the cfrs table discussed in the next section.

If we define \( \mathbf{h}_{i,j}^{\left(UE\right)} = \dfrac{{\rm{iFFT}}_n \left[ \mathbf{H}_{i,j} ^ {\left( UE \right)}\right]}{\sqrt{n}} \) and the geometrically calculated channel impulse response as \( h^{UE}_{i,j} \left(t\right) = \sum_w h^{\left(w \right)}_{i,j} \delta\left(t - \tau^{\left(w \right)}_{i,j} \right) \) we also have \( \left<h^{UE}_{i,j}, h^{UE}_{i,j}\right> = \left<\mathbf{h}_{i,j}^{\left(wb\right)}, \mathbf{h}_{i,j}^{\left(wb\right)}\right> \) where the set of \(\left\{h_{i,j}^{\left(UE\right)}\right\}_{i,j}\) is stored in the raypaths table discussed in the upcoming section.

Finally, if we are interested in calculating the channel frequency response in uplink, we can do so by imposing \( \left<\mathbf{H}_{i,j}^{\left(RU\right)}, \mathbf{H}_{i,j}^{\left(RU\right)} \right>_{UL} = \dfrac{P^{\left(UE\right)}}{N^{\left(UE\right)}_{pol.} \cdot N^{\left(UE\right)}_{hor.} \cdot N^{\left(UE\right)}_{vert.}} \cdot \dfrac{N^{\left(RU\right)}_{pol.} \cdot N^{\left(RU\right)}_{hor.} \cdot N^{\left(RU\right)}_{vert.}}{P^{\left(RU\right)}} \left<\mathbf{H}_{i,j}^{\left(UE\right)}, \mathbf{H}_{i,j}^{\left(UE\right)} \right>_{DL}. \)

Database Replay#

The contents of the database can also be used to visualize or replay previous simulations.

  1. Visualize previous results in the UI: For databases created using release 1.2 or later, click the Replay button in the Configuration tab to view previous simulations results in the UI. This does not require a worker.

  2. Replay simulations with the UI: Retrieve previously configured simulation setups from the database and re-run the simulation using the aodt_sim worker and UI.

  3. Replay simulations without the UI: Replay a simulation using the aodt_sim worker without the UI.

  • NOTE: Check Enable Seeded Mobility in the Scenario Properties to ensure the UE mobility is the same between runs, if desired.

cd <path to aodt backend installation> # e.g. ~/aodt_1.2.0/backend
aerial:~/aodt_1.2.0/backend$ docker compose exec -it connector bash

# note command line options
aerial:/aodt/aodt_sim/build$ python -m aodt.app.sim --help

# replay simulation from a database (note your nucleus and clickhouse hostnames may be different)
aerial:/aodt/aodt_sim/build$ python -m aodt.app.sim --nucleus omniverse://omniverse-server --db-host clickhouse --db-replay --db-name-source <database name, e.g. aerial_2025_1_7_16_40_42>

# run without entering the container (can be used to run several databases via a script)
docker compose exec connector python -m aodt.app.sim --nucleus omniverse://omniverse-server --db-host clickhouse --db-replay --db-name-source <database name>

This way the simulation can also be run with a single GPU shared by the UI and the aodt_sim worker.

  1. Open the UI and attach an aodt_sim worker using the same GPU.

  2. Set up the desired scenario and click Generate UEs, which will save the scenario in the database for replay later.

  • IMPORTANT: If running with single GPU, DO NOT click Start UE mobility as sharing GPU compute and memory between the UI and aodt_sim is not supported during a simulation.

  1. Close the UI so it is no longer using the GPU, then run the simulation using aodt.app.sim in --db-replay mode.

Simulation Mode 2 - RAN simulation#

The RAN simulation mode builds on top of the EM mode and adds key elements of the physical (PHY) and medium access control (MAC) layers. As for the previous mode, all data capturing the detailed interaction between RAN and UEs can be accessed through SQL for further processing or analysis.

To enable the simulation of the RAN, we need to select the Scenario entry under the Stage widget and enable the Simulate RAN checkbox, as shown in the figure below. This will restrict the Simulation mode field in Scenario to Slots.

We can then define the number of batches, number of slots per batch and samples per slot as in EM mode. Specifically,

  • when Samples Per Slot is set to 1, a single front-loaded realization of the channel will be used across the whole slot

  • whereas, when Samples Per Slot is set to 14, every OFDM symbol will be convolved with a different channel realization.

../_images/simulate_ran.png

If the RUs are configured with a 64-antenna panel in the RAN simulation, MU-MIMO is enabled in MAC scheduling. The reader can refer to the MAC Scheduling section for a detailed description and the RAN Parameters table for relevant parameters.

RAN Parameters#

The RAN parameters are stored in

/aodt/aodt_sim/src_be/components/common/config_ran.json

where the following parameters can be changed

Meaning

Default value

gNB noise figure

Noise figure of RU power amplifier

0.5 dB

UE noise figure

Noise figure of UE power amplifier

0.5 dB

DL HARQ enabled

Enables DL HARQ

0

UL HARQ enabled

Enables UL HARQ

0

TDD patterns

Supported TDD patterns, additional patterns can be added

1: DDDSUUDDDD
2: DDDDDDDDDD
3: UUUUUUUUUU

Simulation pattern

Specifies the TDD pattern for simulation

1 (i.e., DDDSUUDDDD)

Max scheduled UEs per TTI - dl

Maximum number of UEs per TTI per cell for DL

6 (max: 6)

Max scheduled UEs per TTI - ul

Maximum number of UEs per TTI per cell for UL

6 (max: 6)

Scheduler Mode

Specifies the PRB scheduling algorithm. Options: “PF” (proportional faireness scheduler) or “RR” (round robin scheduler)

“PF”

Beamformers CSI

Specifies the CSI for beamforming. Options: “SRS” (SRS estimated channels) or “CFR” (ground truth channels)

“CFR”

MAC CSI

Specifies the CSI for L2 MAC scheduling. Options: “SRS” (SRS estimated channels) or “CFR” (ground truth channels)

“CFR”

Beamforming Grp Level

Specifies beamforming group level. Options: “SC” (per subcarrier beamforming), “PRBG” (per PRBG beamforming) or “UEG” (per UEG beamforming)

“PRBG”

pusch channel estimation

Specifies the method for estimating the. Options: “MMSE” or “ML” (machine learning-based estimation)

“MMSE”

channel estimation duration

Specifies the delay spread window size for MMSE channel estimation. Options: 1 (1 us) or 2 (2 us)

2

MCS selection mode

Specifies the method for MCS selection. Currently, only “OLLA” (Outer Loop Link Adaptation) is supported.

“OLLA”

SRS SNR threshold for MU-MIMO feasibility - dl

Applies when 64 antennas at the RU (Radio Unit). Specifies the SRS SNR threshold to determine if a UE qualifies for MU-MIMO grouping in DL.

2 dB

SRS SNR threshold for MU-MIMO feasibility - ul

Applies when 64 antennas at the RU (Radio Unit). Specifies the SRS SNR threshold to determine if a UE qualifies for MU-MIMO grouping in UL

2 dB

Channel correlation threshold for MU-MIMO grouping - dl

Applies when 64 antennas at the RU. Specifies the channel correlation threshold for making MU-MIMO grouping decisions in DL

0.2

Channel correlation threshold for MU-MIMO grouping - ul

Applies when 64 antennas at the RU. Specifies the channel correlation threshold for making MU-MIMO grouping decisions in UL

0.2

Fixed UE layers - dl

If “Enable Fixed Layers” is set to 1, UEs are always assigned the number of layers specified by the “Fixed Layers” parameter in DL

“Enable fixed layers”: 0

Fixed UE layers - ul

If “Enable Fixed Layers” is set to 1, UEs are always assigned the number of layers specified by the “Fixed Layers” parameter in UL

“Enable fixed layers”: 0

RX FT filename

If a filename is provided, the received I/Q samples at the receivers will be saved to an H5 file with the specified filename

default: “” (disabled)

Currently, only a portion of the RAN parameters is available for modification in a convenient JSON file. Subsequent releases, however, will include the full gamut of configuration parameters for all blocks in the RAN.

Simulation#

After the parameters described in config_ran.json are set, we can run the simulation using the same sequence of the EM mode. The results are then propagated to

  • the graphical interface, where we can visualize instantaneous throughput and modulation coding scheme (MCS) for each UE or the RU-level throughput and statistic distribution of the allocated MCSs;

  • the local console, where detailed scheduling information (e.g., PRB allocations and number of layers) are printed slot-by-slot;

  • the selected ClickHouse database, where the full telemetry will be stored.

Graphical user interface#

After the simulation is complete, we can select a specific UE or RU under in the Stage widget and press the play button from the toolbar. In the Property widget, we will see the time series of

  • the instantaneous throughput for UEs,

  • the aggregate instantaneous throughput for the RUs.

../_images/UE_thr.png ../_images/RU_thr.png

Additionally, we can observe the instantaneous throughput of the UE directly above their representation in the viewport, as shown below.

../_images/UE_thr2.png

The MCS allocated by the MAC scheduler for a given UE can also be found right below the instantaneous throughput.

../_images/UE_MCS.png

Similarly, at the RU, we can find the histogram of the modulation and coding schemes in use across the simulation.

../_images/RU_MCS.png

Local console#

If accessible, additional details can be observed in the console where the RAN digital twin is running. At the end of each slot, a table is printed listing all scheduled UEs, PRB allocations (start PRB index and number of allocated PRBs), MCS, number of layers, redundancy version in presence of HARQ, pre-equalization SINR, post-equalization SINR, and CRC results, with 0 denoting a successful decoding.

==============================================  results  ================================================
cell idx   grp idx   ue id     startPrb     nPrb    MCS   layer     RV   sinrPreEq    sinrPostEq     CRC
   0         0         94       176          80       4      2       0     5.67         4.16           0
   0         1         95         4          36       0      2       0    -3.94        -2.43           0
   0         2        155        40          40       3      2       0     1.47         1.10           0
   0         3        175        80          96      27      1       0    34.94        40.00           0
   0         4        192       256          16       1      1       0    -6.14        -1.21           0
   0         5        193         0           4      26      2       0    36.21        26.23        9860658
   1         6         28       200          16      15      1       0    10.12        16.60           0
   1         7         58       216          56      10      1       0     3.95        10.01           0
   1         8         89         0          80      24      1       0    12.64        22.68        7891203
   1         9         92        80          12      27      1       0    35.69        40.00           0
   1        10        178       148          52      12      1       0     6.74        11.62           0
   1        11        184        92          56       7      1       0     2.02         7.28           0
   2        12         34       244          28      27      1       0    34.56        39.19           0
   2        13         47       124          48      15      1       0     6.36        15.37           0
   2        14         60        16          92      16      1       0     5.22        15.24           0
   2        15         68       172          72       9      1       0     3.41         9.45           0
   2        16        194         0          16      11      1       0     3.72        10.74           0
   2        17        199       108          16       3      2       0     9.68         4.18           0
   3        18         23       200           8      27      1       0    31.78        37.85           0
   3        19         56       208          28      20      1       0    14.68        19.20           0
   3        20         57         0          60       3      1       0    -0.52         5.20           0
   3        21         62        60         140      27      1       0    33.20        37.99           0
   3        22        160       260          12      15      1       0    14.91        20.29           0
   3        23        187       236          24      27      1       0    36.80        39.92           0
=========================================================================================================

ClickHouse database#

Comprehensive telemetry data is available in the telemetry table of the database used for the simulation. For instance,

clickhouse-client

aerial :) select * from aerial_2024_4_16_14_28_33.telemetry

SELECT *
FROM aerial_2024_4_16_14_28_33.telemetry

Query id: e463ec6a-4e11-4197-88e0-8762a630181d

┌batch_id─┬─slot_id─┬─link─┬─ru_id─┬─ue_id─┬─startPrb─┬─nPrb─┬─mcs─┬─layers─┬───tbs─┬─rv─┬─outcome─┬───scs─┐─preEqSinr─┬─postEqSinr─┐
│       0        0  UL        0     46        28   132    0       1    372   0        1  30000   8.948093   15.192958 │
│       0        0  UL        0     49         0    28    0       1     80   0        1  30000  30.305893    36.59542 │
│       0        0  UL        0     53       160    24    0       2    141   0        1  30000   35.86527    38.16932 │
│       0        0  UL        0     94       184    88    0       2    497   0        0  30000  -2.340038     0.19586 │
│       0        0  UL        1    124         0   272    0       1    769   0        1  30000  11.380707    9.841505 │
│       0        1  UL        1    124         0   272    9       1   7813   0        1  30000   8.920734   15.117773 │
│       0        1  UL        2     34         0   272    0       1    769   0        1  30000   32.23978   37.145943 │
│       0        1  UL        3     23        20   252    0       1    705   0        1  30000  13.059542   23.430824 │
...

where the meaning of each column is explained in the Database schemas.

Important: Several scripts are available to extract information from the database. Example scripts and notebooks can be found in aodt_sim/examples to extract and plot CIRs/CFRs, throughputs, BLERs, scheduling metrics and other datapoints stored in the tables above.

MAC Scheduling#

The MAC scheduling tasks are performed by Aerial cuMAC, with full support for both UL and DL, HARQ and single-cell as well as multi-cell jointly scheduling. Two different sets of scheduling algorihtms are supported for 4T4R SU-MIMO and 64T64R MU-MIMO simulations, respectively:

  • 4T4R SU-MIMO: MAC scheduler pipeline consists of four modules - UE selection, PRB allocation, layer selection, and MCS selection.

  • 64T64R MU-MIMO: MAC scheduler pipeline consists of three modules - MU-MIMO UE sorting, MU-MIMO UE grouping, and MCS selection.

The data flows for the scheduling process are illustrated in the following figures.

../_images/RAN_MAC_RLC_RRC.png ../_images/scheduler.png

For 4T4R SU-MIMO scheduling, the following procedures are executed in each time slot: first, the necessary input data is provided to cuMAC, and then the four SU-MIMO scheduler functions are run sequentially on the GPU.

  • UE selection: UE down-selection for each cell using the SINRs measured by the RAN PHY.

  • PRB allocation: PRB allocation for the selected UEs per cell using either 1) the SRS channel estimates from the RAN PHY, or 2) the CFRs (ground-truth channels) from the channel emulator engine.

  • Layer selection: layer selection for each selected UE.

  • MCS selection: MCS selection for each selected UE using the SINR measured by the RAN PHY. An outer-loop link adaptation is employed to add a positive/negative offset to the measured SINR. The offset is tuned by the ACK/NACK result of the last scheduled transmission for the given UE.

For 64T64R MU-MIMO scheduling, the following procedures are executed in each time slot: first, the necessary input data is provided to cuMAC, and then the three MU-MIMO scheduler functions are run sequentially on the GPU.

  • MU-MIMO UE sorting: Sorts all active UEs in each cell considering 1) the proportional-fairness (PF) metrics, 2) feasibility for MU-MIMO transmission based on the SRS wideband SNRs measured by the RAN PHY, and 3) HARQ re-transmission status.

  • MU-MIMO UE grouping: for each cell, determines 1) the UEs selected for the current slot, 2) the PRB allocation (subband division) across the band, 3) the UE grouping strategy per subband, 4) the number of layers selected for each UE, and 5) the nSCID (0 or 1) allocated for each layer in a UE group. MU-MIMO UE grouping is done based on the SRS channel estimates from the RAN PHY.

  • MCS selection: MCS selection for each selected UE using the SINR measured by the RAN PHY. An outer-loop link adaptation is employed to add a positive/negative offset to the measured SINR. The offset is tuned by the ACK/NACK result of the last scheduled transmission for the given UE.

Simulation Mode 3 - ML Examples#

In AODT, simulation model 1 and 2 can be used to generate site-specific data for offline analysis or ML training. For instance, after running a simulation, we can import data like channel frequency responses or network performance metrics from the ClickHouse database into data ingestion pipelines for external tools. Additionally, the Aerial Omniverse Digital Twin can also be used to train ML models online, while the simulation is evolving. This is simulation mode 3, and is achieved by having aodt_sim passing

  • simulation state,

  • the position of the UEs and their speed,

  • all the channel data generated by the EM engine,

  • and the telemetry of the RAN at every simulation time step

to Python through bindings.

ML Examples Overview#

Currently, two examples are available depicting different ways of interfacing AI/ML toolchains with AODT:

  • Example 1 illustrates the training of a channel predictor using the channel frequency responses (CFRs) generated by the EM solver. In the example, a neural network learns how to temporally interpolate and extrapolate the CFR from the given time-separated observations of the channel.

  • Example 2 shows the inference of a ML-based channel estimator embedded in the PUSCH pipeline. In the example, a neural network takes as input the least squares estimates of the demodulated reference symbols (DMRS) and predicts the channel values across the whole slot grid.

ML Example 1 - Training a Channel Predictor#

To illustrate how to perform online training in AODT, we can use a minimal example to train a channel predictor based on the channel frequency responses (CFRs) computed by the EM engine.

Channel aging is a well-known problem for reciprocal beamforming, especially for UEs moving with high speed. This is due to the difference in the radio environment between when the channel is sounded and when the base station applies the beamforming weights.  One way to address this problem is to use a neural network to predict the channel when the beamforming weights are planned to be applied.

../_images/ml_ex1.png

To train a neural network to predict the channel, we start by setting the following parameters in the Scenario stage widget.

  • Scenario: 5 UEs and 1 RU

  • Antenna Panels: 2 horizontal, 2 vertical elements, with dual polarization unchecked

  • Batches: 250

  • Slots per batch: 6

  • Sample per slot: 1

  • UE speed: min and max speeds set to 2.0 m/s

  • Enable Training: checked

  • Simulate RAN: unchecked

In this example, the channel predictor treats the channel from each RU and UE antenna pair independently, so we can optionally add more RUs, UEs, and antenna elements, resulting in more channels generated per batch.

Our neural network will estimate the channel 5 slots in advance. That is, given the channel at slot 0, it will predict the channel at slot 5. Thus, we set the slots per batch to 6. We set the number of batches to 250, which provides a good tradeoff between simulation time and achieving a reasonable training loss for this example. At each batch, the UEs are redropped.

Following the steps described for the EM mode, we can click Generate UEs and Start UE mobility to start the simulation. After the simulation finishes, the training and the validation losses are retrieved from the training_result table of the database and shown as part of the properties of Scenario. In the example, such losses are compared to the loss from a LMMSE filter attempting to perform the same action. Such loss appears in the graphical interface as baseline loss.

In the next section, we will go into further detail on how to train a more generic model.

Example - training our own model#

In this section, we will discuss the Python API to train our own model using the Aerial Omniverse Digital Twin. As previously mentioned, the API exposes position information from the UE mobility model and channel information from the EM engine. Thus, it is possible to train any other model that relies on such data. The following discusses the minimum set of functions and data structures to consider when training any of such models.

Specifically, the aodt_sim application makes calls into the Python source code in the channel_predictor directory via Python bindings. The directory includes the following files.

|-- aodt_sim
|-- channel_predictor
|   |-- channel_predictor.py
|   |-- channel_predictor_bindings.cpython-310-x86_64-linux-gnu.so
|   |-- config.ini
|   |-- data_source.py
|   |-- plot_channels.py
|   |-- torch_utils.py
|   |-- train.py
|   |-- trainer.py
|   `-- weiner_filter.py

1 directory, 10 files

Only the Trainer class with member functions scenario and append_cfr in channel_predictor/trainer.py are required for the aodt_sim Python bindings, and must therefore be present in any user-modified Python code. The scenario function is called once at the beginning of a simulation, and the append_cfr function is called for every time step of the simulation. The rest of the files are local to the channel predictor example and do not interface with the aodt_sim application. The reader can refer to the Python docstrings in those files for further details. The user may substitute them with their own Python code.

class Trainer():
    """Class that manages training state"""
    def scenario(self, scenario_info, ru_ue_info):
    """Load scenario and initialize torch parameters

    Args:
        scenario_info (ScenarioInfo): Object containing information about the simulation scenario
        ru_ue_info (RuUeInfo): Object containing information about RUs and UEs

    Returns:
        int: Return code if status was successful (=0) or not (<0)
    """
    def append_cfr(self, time_info, ru_assoc_infos, cfrs):
    """Append channel frequency response (CFR)

    Args:
        time_info (TimeInfo): Simulation time information including batch/slot/symbol
        ru_assoc_infos (list(RuAssocInfo)): List of RU to UE association information
        cfrs (numpy.ndarray): Numpy array of channel frequency response

    Returns:
        TrainingInfo: Result of training including information about number of iterations trained for and losses
    """

Data structures used in Channel Prediction ML Example 1#

The following data structures are passed between aodt_sim and the Trainer class.

ScenarioInfo#

Field

Type

Comment

slot_symbol_mode

bool

True: slot/symbol simulation mode,
False: duration/interval simulation mode

batches

uint32

Number of batches defined in Scenario

slots_per_batch

uint32

Number of slots per batch defined in Scenario

symbols_per_slot

uint32

Number of samples per slot defined in Scenario

duration

float32

Simulation duration computed based on slots_per_batch and symbols_per_slot

interval

float32

Interval computed based on slots_per_batch and symbols_per_slot

ue_min_speed_mps

float32

UE minimum speed in m/s

ue_max_speed_mps

int32

UE maximum speed in m/s

seeded_mobility

int32

Whether or not to use a seed when randomizing UE mobility

seed

int32

Value of the seed to use when randomizing UE mobility

scale

float64

Scale factor when converting to the units used in the USD scene (typically centimeters)

ue_height_m

float32

UE height in meters

RuUeInfo#

Field

Type

Comment

num_ues

uint32

Number of RUs in simulation

num_rus

uint32

Number of UEs in simulation

ue_pol

uint32

Number of UE antenna polarizations

ru_pol

uint32

Number of RU antenna polarizations

ants_per_ue

uint32

Number of antennas per UE

ants_per_ru

uint32

Number of antennas per RU

fft_size

uint32

Number of frequency samples in channel frequency responses

numerology

uint32

Numerology (μ) as defined in 3GPP 38.211

TimeInfo#

Field

Type

Comment

time_id

uint32

Current time index of simulation

batch_id

uint32

Current batch index of simulation

slot_id

uint32

Current slot index of simulation

symbol_id

uint32

Current symbol index of simulation

UeInfo#

Field

Type

Comment

ue_index

uint32

UE index (starts from 0)

ue_id

uint32

User ID as defined in the stage widget

position_x

float32

Current UE x position in the stage

position_y

float32

Current UE y position in the stage

position_z

float32

Current UE z position in the stage

speed_mps

float32

Current UE speed in meters per second

RuAssocInfo#

Field

Type

Comment

ru_index

uint32

RU index (starts from 0)

ru_id

uint32

RU ID as defined in the stage widget

associated_ues

list(UeInfo)

List of UEs associated to this RUs

CFRs#

Field

Type

Comment

cfrs

list(numpy.ndarray((ues, ue_ants, ru_ants, fft_size), dtype=numpy.complex64))

Channel frequency response for all RUs in a list. The elements of the list are multi-dimensional arrays of the form [ue, ue_ant, ru_ant, fft_size]

TrainingInfo#

Field

Type

Comment

time_id

uint32

Current time index of simulation

batch_id

uint32

Current batch index of simulation

slot_id

uint32

Current slot index of simulation

symbol_id

uint32

Current symbol index of simulation

name

string

Optional name of model, e.g. “Channel Predictor”

num_iterations\(^{*}\)

float32

Number of iterations that were trained in this time step of the simulation. An iteration is here defined as one forward and one backward pass through the model.

training_losses

list(tuple(uint32, float32))

Training losses: there may be multiple iterations trained for a given time index, so format is [(iteration0, loss0), (iteration1, loss1), …]

validation_losses

list(tuple(uint32, float32))

Optional validation losses, same format as training losses

test_losses

list(tuple(uint32, float32))

Optional test losses, same format as training losses

baseline_losses

list(tuple(uint32, float32))

Baseline losses, same format as training losses

title

string

Optional title of loss plot in the UI, e.g. Training Loss

y_label

string

Optional y-label of loss plot in the UI, e.g. MSE (dB)

x_label

string

Optional x-label of loss plot in the UI, e.g. Slot

As previously mentioned, additional APIs to train RAN models will be added in subsequent releases.

Note: let us consider a setup consisting of 4 RUs, 4 UEs, 4 ants per UE, 4 ants per RU. There will be 256 CFRs computed every time step of the simulation. The channel predictor considers each RU/UE antenna pair independently. If we configure the prediction to be 6 slots in advance, then slots 0-4 will be spent accumulating data. For a training batch size of 16, there will thus be enough data to train 256 CFRs / 16 (training batch size) = 16 training iterations (16 forward/backward passes) on slot 5.

ML Example 2 - Inferencing a Channel Estimator#

Example 2 illustrates how we can bring a machine learning model developed with NVIDIA’s Sionna into the Aerial Omniverse Digital Twin and assess its performance at inference time.

Specifically, in our example,

  1. we have trained a neural network using NVIDIA’s Sionna and a stochastic channel model offered by the software (Urban Macro or UMa).

  2. we have leveraged NVIDIA’s PyAerial to integrate our design into the PUSCH receiver and SRS in AODT.

  3. and, finally, we have assessed the performance of the model as system level, using the site-specific channel produced by the EM solver.

The following figure illustrates the final system running in AODT, with the ML channel estimator taking the demodulated reference symbols (DMRS) from the signal produced by the channel emulator and producing a channel estimate across the whole frequency / time grid of the OFDM waveform.

../_images/ml_ex2.png

Running the ML PUSCH Channel Estimation Example#

Three steps are required to run the example.

Step 1 - Generate the models

To generate the channel estimator models, use the PyAerial Channel Estimation notebook in aodt_sim/external/cuBB/pyaerial/notebooks/channel_estimation. The notebook should be run with interp=2 for PUSCH DMRS, and different SNRs and PRBs by setting, e.g., train_snrs=[-10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40] and num_prbs = 1. The process should be repeated for each number of PRBs, e.g., once for each element of the set {1, 4, 16}. During inference, the channel estimation module will combine the models to match the PRB allocation from the scheduler. At a minimum, a 1 PRB model must be available to handle uneven allocations, and larger PRBs are to optimize for larger allocations.

This produces a different model for each one of the SNR and PRB points, which we will then use in our switching approach that selects the right model based on the signal. More details can be found in the notebook.

Step 2 - Make the models accessible to AODT

Once the model have been generated, they need to be exposed to aodt_sim. This requires

  • setting the parameter freq_interp_factor to 2 in config_est.ini for PUSCH DMRS.

  • moving the folder containing the models inside the container with the docker cp command

  • adding the absolute path of the models folder in file config_est.ini; e.g., models_folder = '/home/saved_models'.

The models for different PRBs should have the number of PRBs in the directory name, e.g. prbs=<num_prbs>. The PRB subdirectories will be discovered when the models are first loaded at inference time. For example:

tree /home/saved_models
...
└── saved_models_prbs=1_interp=4
    └── ch=UMa_n_iter=20000_batchsize=32_model=1PRBs
│       ├── model_SNR=-10.0.path
│       ├── model_SNR=-5.0.path
...
├── saved_models_prbs=4_interp=4   └── ch=UMa_n_iter=20000_batchsize=32_model=4PRBs
│       ├── model_SNR=-10.0.path
│       ├── model_SNR=-5.0.path
...
└── saved_models_prbs=16_interp=4
    └── ch=UMa_n_iter=20000_batchsize=32_model=16PRBs
│       ├── model_SNR=-10.0.path
│       ├── model_SNR=-5.0.path
...

Step 3 - Configure the RAN simulation

In the graphical interface, the only configuration needed is setting Simulate RAN to enabled.

For backend configurations, we need to change three fields in src_be/components/common/config_ran.json:

  • “Simulation pattern”: 3,

  • “pusch channel estimation”: “ML”

  • “Fixed UE layers - ul”: { “Enable fixed layers”: 1, “Fixed layers”: 1 }

This configuration ensures that the TDD split is comprised of only UL slots, which facilitates testing since the channel estimation is integrated in the PUSCH receiver pipeline. For more details about these configurations, refer to the tables in the RAN Parameters section of this document.

Running the ML SRS Channel Estimation Example#

Similar to the PUSCH, we integrated the same channel estimation models in SRS. This feature is currently experimental, but we provide instructions below for its execution:

  1. Generate the models: The same as in PUSCH, with the following differences:

    • the interp variable in PyAerial Channel Estimation notebook should be set to 4, since SRS in AODT currently uses comb size 4.

    • the number of PRBs should be equal to 272 to match the SRS configuration in aodt_sim.

  2. Make the models accessible to AODT: Also the same as in PUSCH, except that the parameter freq_interp_factor in config_est.ini should be set to 4 to match the model training in the PyAerial notebook.

  3. Configure the RAN simulation: The config_ran.json fields that require changes from their default configurations are:

  • “Simulation pattern”: 1,

  • “Beamformers CSI”: “SRS+ML”

  • “MAC CSI”: “SRS+ML”

Note that the beamformers are only affected after the S slot, because the SRS is only being transmitted in this slot.

Building on top of the PUSCH ML Channel Estimator#

The source code for this example is in aodt_sim/src_ml/channel_estimation. The example can be extended to replace other blocks of the PUSCH receiver. The following files are relevant:

File

Description

app/sim_main.cpp

The main file where simulation starts.

src/asim_loop.cpp

Where the main first-level functions are defined. This file contains handler_play and handler_play_full_sim, i.e., the functions that define the simulator’s behavior running in Mode 1 (EM) and Mode 2 (RAN). The logic for ML example 2 is in handler_play_full_sim, and the logic for ML example 1 is largely present in asim_loop.cpp.

src_be/controller/src/be_ctrl.cu

Contains the backend controller for RAN functions. This file calls the cuPHY PUSCH receiver and transmitter, as well as the modularized/component-wise counterparts provided by PyAerial. The file ml_estimator.cpp (below) interfaces AODT with the machine learning channel estimator. Additionally, this file contain the logic for converting a cuPHY GPU tensor used by PyAerial to NumPy arrays which can be interpreted and processed in Python. The inverse conversion is done after estimation, along with copying the channel estimates to the right variable to be used by the PUSCH receiver.

src_ml/channel_estimator/ml_estimator.cpp

Contains the wrapper classes and the pybind11 bindings. The wrappers are called during the simulation in C++ to instantiate a Python interpreter and perform initialization, model loading and estimation.

src_ml/channel_estimator/ml_estimator.hpp

Declares the classes that will be populated with information for the channel estimation.

src_ml/channel_estimator/ch_estimator_class.py

Main Python file defining the logic for instantiating an estimator object, verify the correct paths to the machine learning models, load those models, and perform estimation.

src_ml/channel_estimator/ch_estimator_model.py

Where the channel estimator model and loss function are defined.

Data structures used in Channel Prediction ML Example 2#

PuschEstimatorInfo contains

  • the LS and MMSE channel estimates to pass to the ML channel estimator,

  • RU information RuInfo,

  • and UE group information UeGroupInfo.

Here, a UE group is defined as a group of co-scheduled UEs that share the same time-frequency resources.

UeGroupInfo#

Field

Type

Comment

group_idx

uint32

Absolute group index/identifier

ru_idx

uint32

Absolute RU index/identifier, as defined in the UI

num_tot_layers

uint32

Total number of layers in the UE group, derived from the sum of the layers of each UE

num_prbs

uint32

Number of PRBs shared by the UE group*

num_symbs

uint32

Number of DMRS symbols in one slot - either 1 or 2

*Note: all UEs in the UE group are assumed to use the same number of PRBs. This is less general than assuming each UE can have different band allocations, but simplifies the approach significantly.

RuInfo#

Field

Type

Comment

num_rx_ants

uint32

Number of RX antennas in the RU

num_ue_groups

uint32

Number of UE groups in the RU. If beamforming is disabled, the number of UE groups equals the number of UEs

ue_groups

list(UeGroupInfo)

List of UE group scheduled by the RU

PuschEstimatorInfo#

Field

Type

Comment

num_rus

uint32

Number of RUs

num_tot_groups

uint32

Total number of UE groups across all RUs

ru_infos

list(RuInfo)

List of size num_rus with information about each one of the RUs in the simulation

ls_estimates

list(ndarray(complex64))

List of size num_tot_groups where each element is a NumPy array. For the \(i\)-th group, the dimensions of the array are [num_prbs \(_i\) x 6, num_tot_layers \(_i\), num_rx_ant \(_i\), num_symbs \(_i\)] of type complex64 in NumPy.

mmse_estimates

list(ndarray(complex64))

List of size num_tot_groups containing the channel estimates for each UE group. For the \(i\)-th group, each element of the list is a NumPy array of size [num_rx_ant \(_i\), num_tot_layers \(_i\), num_prbs \(_i\) x 12, num_symbs \(_i\)] of type complex64 in NumPy.