NVlabs / sionna

Sionna: An Open-Source Library for Next-Generation Physical Layer Research
https://nvlabs.github.io/sionna
Other
737 stars 211 forks source link

Coverage map non-contiguous problem #517

Closed Hongcheng-Dong closed 2 months ago

Hongcheng-Dong commented 2 months ago

Hello, I noticed that the graph generated by sionna's scene.coverage_map function will produce discontinuities at the edge, such as shown in your example graph. This seems to contradict the actual ray tracing process (there are direct paths to the surrounding positions but not in the middle).
image

Is it a problem with my parameter settings? My code for this part is as follows:

cm = scene.coverage_map(
        rx_orientation=[0., 0., 0.],
        max_depth=10,
        cm_center=[0, 0, 1.5],
        cm_orientation=[0., 0., 0.],
        cm_size=[x_max - x_min, y_max - y_min],
        cm_cell_size=[grid_size_x, grid_size_y],
        combining_vec=None,
        precoding_vec=None,
        num_samples=int(1e7),
        los=True,
        reflection=True,
        diffraction=True,
        scattering=True,
        ris=True,
        edge_diffraction=True,
        check_scene=True
        )
faycalaa commented 2 months ago

Hi @Hongcheng-Dong ,

Assessing the correctness of the coverage map only based on this visualization is challenging, as coverage depends on the geometry of the surfaces of the buildings where these paths are reflected.

To check the accuracy of the coverage map, you could position a receiver in the area that appears to have little coverage, then, compute a channel impulse response (CIR) using Scene.compute_paths for this receiver and compare the total path loss of the CIR against the coverage map value.

Note however that the path loss indicated by the coverage map is an approximation of the path loss you would obtain by positioning a receiver and calculating the path loss from the CIR. However, these two quantities should align for small enough cells and a large enough number of samples used to compute the coverage map.

Hongcheng-Dong commented 2 months ago

Hello,
I checked all the parameters and found that the problem was with coverage_map itself. The pathloss[0,253] generated by coverage_map is -inf, but the pathloss calculated by Scene.compute_paths and paths.cir is -51.15, which is greater than the DISCARD_THRES=-150dB set by coverage_map, but the pathloss[0,252] and [0,254] ​​generated by coverage_map can get the correct value.

I tried to use all Tx and Rx pairs to calculate the correct Path Loss, but the time required was huge.

faycalaa commented 2 months ago

Could you share a minimal and standalone code snippet that reproduces the issue?

Hongcheng-Dong commented 2 months ago
import sionna
from sionna.rt import load_scene, PlanarArray, Transmitter, Receiver
import numpy as np

scene = load_scene(sionna.rt.scene.munich)

scene.tx_array = PlanarArray(num_rows=8,
                        num_cols=2,
                        vertical_spacing=0.7,
                        horizontal_spacing=0.5,
                        pattern="tr38901",
                        polarization="VH")

scene.rx_array = PlanarArray(num_rows=1,
                        num_cols=1,
                        vertical_spacing=0.5,
                        horizontal_spacing=0.5,
                        pattern="dipole",
                        polarization="cross")
tx = Transmitter(name="tx",
            position=[8.5,21,30],
            orientation=[0,0,0])
scene.add(tx)

#########################################################
cm = scene.coverage_map(cm_cell_size=[1., 1.],
                    num_samples=int(10e6))

pathloss1 = 10 * np.log10(cm[0].numpy())
passloss_coverage_map = pathloss1[652, 745]  # is -Inf

########################################################
rx = Receiver(name='rx',
              position=[-652 + 1206/2, 745 - 1476/2, 30],
              orientation=[0, 0, 0])

scene.add(rx)

paths = scene.compute_paths(num_samples=int(10e6))
a, tau = paths.cir()
rx_power_linear = 0.0
num_paths = a.shape[-1]
for i in range(num_paths):
    path_amplitude = np.abs(a[..., i])
    path_power = path_amplitude ** 2
    rx_power_linear += np.sum(path_power)

passloss_rx = 10 * np.log10(rx_power_linear)

I'm not sure if the rx positions correspond to the grid positions of the coverage map, but they are consistent for the purpose of illustrating the point I'm trying to make.

faycalaa commented 2 months ago

Hi @Hongcheng-Dong,

I took a closer look at your code and identified a few issues.

Firstly, the CoverageMap object includes a cell_centers property, which provides the centers of each cell. In your code, the receiver you instantiated to measure the channel impulse response is not correctly positioned. Specifically, its elevation is set to 30m, matching the elevation of the transmitter. However, the coverage map is measured at the default elevation of 1.5m.

Additionally, note that the Paths.cir() function returns channel coefficients where size of the last dimension corresponds to the number of time steps, not the number of paths.

By correcting these two elements, Scene.compute_paths() does not find any paths for the cell with no coverage.

import sionna
from sionna.rt import load_scene, PlanarArray, Transmitter, Receiver
import numpy as np

%matplotlib widget
import matplotlib.pyplot as plt

scene = load_scene(sionna.rt.scene.munich)

scene.tx_array = PlanarArray(num_rows=8,
                             num_cols=2,
                             vertical_spacing=0.7,
                             horizontal_spacing=0.5,
                             pattern="tr38901",
                             polarization="VH")

scene.rx_array = PlanarArray(num_rows=1,
                             num_cols=1,
                             vertical_spacing=0.5,
                             horizontal_spacing=0.5,
                             pattern="dipole",
                             polarization="cross")
tx = Transmitter(name="tx",
                 position=[8.5,21,30],
                 orientation=[0,0,0])
scene.add(tx)

cm = scene.coverage_map(cm_cell_size=[1., 1.],
num_samples=int(1e7))

pl_db = 10 * np.log10(cm[0].numpy())
pl_one_val = pl_db[652, 745] # is -Inf
print("Path loss [dB] at (652, 745):", pl_one_val)

rx = Receiver(name='rx',
              position=cm.cell_centers[652, 745], # Use the position of the cell center
              orientation=[0, 0, 0])
scene.add(rx)

paths = scene.compute_paths(num_samples=int(1e7))
a, tau = paths.cir()
print("Number of paths", a.shape[-2]) # Size of the second to last dimension corresponds to the number of paths

Output:

Path loss [dB] at (652, 745): -inf
Number of paths: 0
Hongcheng-Dong commented 2 months ago

I'm sorry that there are some problems with my code, but I used your modified code and replaced a grid point [647,748], and found that the coverage map was inconsistent with the path.

import sionna
from sionna.rt import load_scene, PlanarArray, Transmitter, Receiver
import numpy as np

scene = load_scene(sionna.rt.scene.munich)

scene.tx_array = PlanarArray(num_rows=8,
                             num_cols=2,
                             vertical_spacing=0.7,
                             horizontal_spacing=0.5,
                             pattern="tr38901",
                             polarization="VH")

scene.rx_array = PlanarArray(num_rows=1,
                             num_cols=1,
                             vertical_spacing=0.5,
                             horizontal_spacing=0.5,
                             pattern="dipole",
                             polarization="cross")
tx = Transmitter(name="tx",
                 position=[8.5, 21, 30],
                 orientation=[0, 0, 0])
scene.add(tx)
cm = scene.coverage_map(cm_cell_size=[1., 1.],
                        num_samples=int(10e6))

pathloss1 = 10 * np.log10(cm[0].numpy())
pl_one_val = pathloss1[647, 748]  # is -Inf

print("Path loss [dB] at (647, 748):", pl_one_val)

rx = Receiver(name='rx',
              position=cm.cell_centers[647, 748],  # Use the position of the cell center
              orientation=[0, 0, 0])
scene.add(rx)

paths = scene.compute_paths(num_samples=int(1e7))
a, tau = paths.cir()
print("Number of paths", a.shape[-2])  # Size of the second to last dimension corresponds to the number of paths

P_t = 1.0
P_r = np.sum(np.abs(a)**2)
# Compute the path loss
path_loss_dB = 10 * np.log10(P_r / P_t)
print("Path Loss (dB):", path_loss_dB)

Output:

Path loss [dB] at (647, 748): -inf
Number of paths: 3
Path Loss (dB): -94.36647962271647
faycalaa commented 2 months ago

As mentioned earlier, the path loss indicated by the coverage map is an approximation of the path loss you would obtain by positioning a receiver and calculating the path loss from the CIR. These two quantities should align for sufficiently small cells and a large enough number of samples used to compute the coverage map. Detailed information on how the coverage map is computed can be found here.

Given that you have selected a small cell size ($1m \times 1m$), the number of samples configured might not be sufficient to accurately reconstruct the path loss in all cells, particularly in areas with low coverage, such as the cell you have selected.

You can try increasing the number of samples used to compute the coverage map. Another option is to increase the cell size, which effectively interpolates the coverage from neighboring areas.

Hongcheng-Dong commented 2 months ago

Thank you, it seems that increasing num_samples is the easiest way to deal with it, although there will still be errors at certain grid points.