Wall reflection link
Check Channel against the image method with a transmitter, a receiver and a metal wall, where every delay, reflection point and coefficient has a closed form.
This tutorial builds the simplest scene with a reflection and checks Channel's answer against a hand calculation. It shows how to read a PathTable and what convention the complex coefficients use. It takes about one minute, including the first-call compile.
The scene#
A 6 m by 3 m metal wall stands in the plane x = 2.5. The transmitter is at (0, -1, 0.5) and the receiver at (0, 1, 0.5), both 2.5 m in front of the wall. Two paths connect them: line of sight, and one reflection off the wall.
The image method gives the reflected path exactly: mirror the transmitter in the wall plane, to (5, -1, 0.5), and draw a straight line from the image to the receiver. That line crosses the wall at the reflection point (2.5, 0, 0.5), and its length, sqrt(5**2 + 2**2) = 5.385 m, is the reflected path length.
Solve and compare#
import cmath
import math
import torch
from witwin.core import AntennaState, Mesh, PhysicalMaterial, Scene, Structure
from witwin.channel import deterministic
FREQUENCY_HZ = 3.0e9
WAVELENGTH_M = 299_792_458.0 / FREQUENCY_HZ
# A 6 m x 3 m metal wall in the plane x = 2.5.
vertices = torch.tensor([[2.5, -3.0, -1.0], [2.5, 3.0, -1.0], [2.5, -3.0, 2.0], [2.5, 3.0, 2.0]])
faces = torch.tensor([[0, 1, 2], [1, 3, 2]])
wall = Structure(Mesh(vertices, faces, recenter=False, fill_mode="surface"), PhysicalMaterial.perfect_conductor(), name="wall")
tx = AntennaState(1, "tx", torch.tensor([0.0, -1.0, 0.5]), power_w=1.0)
rx = AntennaState(2, "rx", torch.tensor([0.0, 1.0, 0.5]))
scene = Scene(structures=[wall], endpoints=[tx, rx])
config = deterministic.Config(max_depth=1, components={"los", "reflection"}, export_paths=True)
result = deterministic.solve(scene, config, reference_frequency_hz=FREQUENCY_HZ)
paths = result.paths
for k in range(paths.delay_s.shape[0]):
print(f"component {paths.component_id[k].item()} length {paths.path_length_m[k].item():.4f} m "
f"delay {paths.delay_s[k].item() * 1e9:.3f} ns gain {paths.path_gain[k].item():.4e}")
print("reflection point", [round(v, 3) for v in paths.interaction_position[1].tolist()])
# Image method: mirror the transmitter in the wall, then use free-space spreading.
image = torch.tensor([5.0, -1.0, 0.5])
lengths = {"los": 2.0, "reflection": (image - rx.position.cpu()).norm().item()}
wavenumber = 2 * math.pi / WAVELENGTH_M
expected = {
"los": WAVELENGTH_M / (4 * math.pi * lengths["los"]) * cmath.exp(-1j * wavenumber * lengths["los"]),
# A perfect conductor reflects the tangential (here vertical) field with coefficient -1.
"reflection": -WAVELENGTH_M / (4 * math.pi * lengths["reflection"]) * cmath.exp(-1j * wavenumber * lengths["reflection"]),
}
for k, name in enumerate(expected):
print(f"{name:10s} Channel {complex(paths.coefficient[k].item()):.6f} image method {expected[name]:.6f}")
print(f"total gain Channel {result.path_gain.item():.4e} image method {abs(sum(expected.values())) ** 2:.4e}")Output (RTX 5080):
component 0 length 2.0000 m delay 6.671 ns gain 1.5810e-05
component 1 length 5.3852 m delay 17.963 ns gain 2.1806e-06
reflection point [2.5, 0.0, 0.5]
los Channel 0.003961-0.000345j image method 0.003961-0.000345j
reflection Channel -0.001131-0.000949j image method -0.001131-0.000949j
total gain Channel 9.6821e-06 image method 9.6823e-06Every number matches the hand calculation.
What the output shows#
- Rows and components. The
PathTablehas one row per path.component_id0 is line of sight and 1 is reflection; see component codes. Rows are sorted by receiver, transmitter, depth and component. - Delays.
delay_sis the path length divided by the speed of light: 2 m gives 6.671 ns, 5.385 m gives 17.963 ns. - Per-path gain.
path_gainof a row is the squared magnitude of its coefficient. For line of sight it is the Friis value(wavelength / (4 * pi * d))**2. The metal wall reflects all power, so the reflected row has the Friis value for 5.385 m. - Coefficient convention. A path's complex coefficient is its amplitude times
exp(-1j * k * d), wherekis the wavenumber anddthe path length, times the coefficient of each interaction. Here the reflection adds a factor -1: both antennas default to vertical polarization (see Scenes and geometry), and a perfect conductor reverses the field tangential to its surface. - Coherent total.
result.path_gainisabs(sum of coefficients)**2, so the two paths interfere. Here the total, 9.68e-6, is below the line-of-sight gain alone.
Try next#
- Replace
PhysicalMaterial.perfect_conductor()withPhysicalMaterial(eps_r=5.24, sigma_e=0.12). The reflected gain drops to the Fresnel reflection loss of the material; the delay stays the same. - Move the receiver behind the wall (
x > 2.5) and add"transmission"with a thin-sheet material. See Model walls and materials. - Solve the same scene with
path.solveto get the coefficients asaand the delays astau, then compute the CIR and CFR. See Extract paths, CIR and CFR.