Dynamic Scenario Pipeline Documentation
This document provides a full reference for the dynamic-scenario generator built on top of DeepMIMO data. It covers architecture, interpolation logic, CLI usage, programmatic entry points, and details on the saved outputs.
Contents
- Overview
- Core Modules
- Execution Flow
- CLI Usage
- Programmatic Usage
- Output Structure
- Trajectory Behaviour and Interpolation Details
- Visualization and Plot Styling
- Extending the Pipeline
- Troubleshooting
1. Overview
The dynamic scenario generator produces per-user channel sequences by interpolating DeepMIMO user-grid data along simulated vehicle and pedestrian trajectories. The main goals are:
- respect the road layout implicit in the DeepMIMO user grid;
- simulate realistic motion (straight segments, turns at intersections, stops at dead ends);
- interpolate channel ray parameters between user locations;
- save both the complex channels and rich metadata (positions, velocities, Doppler, etc.).
Artifacts are stored under examples/ by default and include a “slim” channel file, a full metadata file, and a visualization of the environment.
2. Core Modules
deepmimo_adapter.py
Responsible for loading DeepMIMO v4 datasets and returning data in a backward-compatible format:
- Supplies
load_deepmimo_user_data(scenario, scenarios_dir, max_paths, load_params, array_dtype, logger). - Handles multiple TX sets by selecting the one with the highest aggregate power.
- Converts object arrays returned by DeepMIMO into stacked numpy arrays (per-user matrices).
- Exposes user data through
_LazyPathAccessorso legacy code can readpaths[idx]and receive a dict matching the DeepMIMO v3 format (power,phase,ToA,DoA_theta,DoA_phi,num_paths,LoS, etc.).
scenario_generation.py
Contains the main orchestration logic:
AntennaArrayConfig,TrafficConfig,GridConfig,ScenarioSamplingConfig,ScenarioGenerationConfig: dataclasses describing configuration state.DynamicScenarioGenerator.generate(overwrite=False): entry point that produces aScenarioGenerationResultwith file paths and the in-memory payload.- Helper functions handling geometry and interpolation.
3. Execution Flow
Step 1: Loading DeepMIMO data
deepmimo_data = load_deepmimo_user_data(
cfg.scenario,
scenarios_dir=cfg.scenarios_dir,
load_params=cfg.deepmimo_load_params,
max_paths=cfg.deepmimo_max_paths,
array_dtype=cfg.deepmimo_array_dtype,
logger=self.logger,
)
path_exist = np.asarray(deepmimo_data["user"]["LoS"])
pos_total = np.asarray(deepmimo_data["user"]["location"])
path_existindicates LoS (1), NLoS (0), or no path (-1) per user grid point.pos_totalis the xyz coordinate of each user (meters).
Step 2: Inferring road geometry
_infer_grid_step(positions)— ifGridConfig.auto_step_sizeis true, computes a typical spacing between user-grid points using k-NN distances; otherwise uses the user-providedGridConfig.step_size.filter_road_positions(...)— filters the user points that lie inside vertical or horizontal “bands” defined byroad_widthandroad_center_spacing. Points falling inside both bands are tagged as intersections.create_grid_road_network(...)— constructs a directed graph (NetworkX) connecting road nodes separated by roughly onegrid_step, respecting lane directions and turn allowances.
Step 3: Generating trajectories
- Vehicles:
generate_n_smooth_grid_trajectorieswalks the graph:- chooses random start nodes biased toward the interior of the road graph (KD-tree sampling);
- at each step, picks forward edges if available, optionally turning at intersections with probability
turn_probability; - if no forward neighbor exists (dead end, trapped node, etc.), the walk terminates early and that trajectory attempt is discarded — another start node will be sampled until enough valid paths are found or
max_attemptsis reached.
- Pedestrians:
generate_n_pedestrian_trajectoriesperforms shorter random walks over valid positions using a step size and angular noise. - Mapping back to DeepMIMO indices:
get_trajectory_indicesbuilds a dictionary from positions to DeepMIMO user indices and maps each trajectory to a list of grid user IDs.
Step 4: Sampling continuous motion
_build_tracks(...)
- Draws a nominal speed per vehicle/pedestrian within the configured range.
- Calls
sample_continuous_along_polylinewith(trajectory positions, user indices, nominal speed, sample_dt, horizon, speed_profile=None):- constructs cumulative arc lengths along the polyline;
- advances a fixed
speed * dtdistance per sample (clamped to the end of the polyline); - finds the corresponding segment and interpolation factor
alpha, yielding continuous positions between grid points; - stores the segment endpoints as
(user_idx0, user_idx1)along withalphaand the segment direction vector.
- After resampling, per-sample velocities are recomputed from the actual XY displacement (
step_xy / dt) so Doppler reflects the true motion; accelerations are the finite differences of this velocity series. - Each track dictionary contains
speed,pos,pairs,alpha,vdir, the derivedvel/acc, andstep_xy.
Step 5: Interpolating ray parameters
In _channels_for_tracks, for each sample:
i0, i1 = pair
ray_interp = interpolate_ray_params(deepmimo_data, i0, i1, float(alpha))
- Finds the two user entries (
deepmimo_data["user"]["paths"][i0],[i1]). - Selects the top
min(num_paths[i0], num_paths[i1])rays by power. - Interpolates:
- powers, ToAs: linear blend (
(1-α) * val0 + α * val1). - angles: unwraps to avoid jumps before blending.
- phase: blends via complex exponentials to maintain continuity.
- LoS flag: OR between the two user LoS flags.
- powers, ToAs: linear blend (
- Computes per-path Doppler:
v_proj = -⟨aoa_unit, vdir⟩ * instantaneous_speed. This ties Doppler to the actual speed profile. - Populates
ray_interpwithDoppler_velandelapsed_time = k * sample_dt.
Step 6: Channel synthesis
pred, _, _ = generate_channel_from_interpolated_ray(ray_interp, antenna_cfg, carrier_frequency_hz)
channel_sequence.append(np.asarray(pred[0]).squeeze(0))
- Builds DeepMIMO parameter sets for Tx/Rx arrays and OFDM configuration.
- Calls
generate_mimo_channel, which applies Doppler and phase updates in the frequency domain. - Each sample yields a complex tensor
[n_tx_ant, n_subcarriers](Rx is collapsed to 1 by default).
Step 7: Packaging outputs and plotting
- Channels, positions, velocities, accelerations, Doppler, angles, delays are stacked across tracks.
- Simplified channel (
channel_contorchannel) is saved atoutput_path; the entire metadata dict (payload) goes tofull_output_path. - Environment plot (
environment.png) shows LoS/NLoS users, road nodes, trajectories, start markers. Discrete trajectories are drawn as polylines; the speed profile influences the sampled positions.
4. CLI Usage
Main entry point: python -m LWMTemporal.cli.generate_dynamic_data. Key flags:
--scenario-name,--scenarios-dir.- Road inference:
--road-width,--road-spacing,--grid-step,--disable-auto-grid-step. - Traffic:
--num-vehicles,--num-pedestrians,--vehicle-speed,--ped-speed,--turn-probability,--max-attempts. - Sampling:
--time-steps,--sample-dt,--continuous-length. - DeepMIMO:
--max-paths,--scenarios-dir,--scenario-range. - Output directories:
--output-dir,--full-output-dir,--figures-dir. Defaults:examples/data,examples/full_data,examples/figs. - Misc:
--regen-dims,--channel-dims(CSV),--seed,--overwrite,--log-dir.
Example:
python -m LWMTemporal.cli.generate_dynamic_data \
--scenario-name city_1_losangeles_3p5 \
--scenarios-dir deepmimo_scenarios \
--num-vehicles 200 \
--num-pedestrians 15 \
--time-steps 20 \
--sample-dt 0.001 \
--tx-horizontal 32 \
--tx-vertical 1 \
--subcarriers 32 \
--road-width 2 \
--road-spacing 8 \
--vehicle-speed 0 30 \
--turn-probability 0.1 \
--max-paths 25 \
--figures-dir examples/figs \
--overwrite
5. Programmatic Usage
5.1 Using the CLI main() function
from LWMTemporal.cli.generate_dynamic_data import main
main([
"--scenario-name", "city_0_newyork_3p5",
"--scenarios-dir", "deepmimo_scenarios",
"--num-vehicles", "120",
"--num-pedestrians", "20",
"--time-steps", "40",
"--sample-dt", "0.1",
"--tx-horizontal", "32",
"--subcarriers", "32",
"--output-dir", "examples/data",
"--full-output-dir", "examples/full_data",
"--figures-dir", "examples/figs/newyork",
"--overwrite",
])
5.2 Direct generator invocation
from pathlib import Path
from LWMTemporal.data.scenario_generation import (
AntennaArrayConfig,
DynamicScenarioGenerator,
GridConfig,
ScenarioGenerationConfig,
ScenarioSamplingConfig,
TrafficConfig,
)
config = ScenarioGenerationConfig(
scenario="city_0_newyork_3p5",
antenna=AntennaArrayConfig(tx_horizontal=32, tx_vertical=1, subcarriers=32),
sampling=ScenarioSamplingConfig(time_steps=20, sample_dt=0.01),
traffic=TrafficConfig(num_vehicles=120, num_pedestrians=20, turn_probability=0.1),
grid=GridConfig(road_width=2.0, road_center_spacing=8.0),
output_dir=Path("examples/data"),
full_output_dir=Path("examples/full_data"),
figures_dir=Path("examples/figs/newyork"),
scenarios_dir=Path("deepmimo_scenarios"),
deepmimo_max_paths=25,
)
generator = DynamicScenarioGenerator(config)
result = generator.generate(overwrite=True)
dataset = result.payload
dataset is the same dictionary saved in the _full.p file.
5.3 Notebook quick start
from pathlib import Path
from LWMTemporal.data.scenario_generation import (
AntennaArrayConfig,
DynamicScenarioGenerator,
GridConfig,
ScenarioGenerationConfig,
ScenarioSamplingConfig,
TrafficConfig,
)
config = ScenarioGenerationConfig(
scenario="parow",
antenna=AntennaArrayConfig(tx_horizontal=32, tx_vertical=1, subcarriers=32),
sampling=ScenarioSamplingConfig(time_steps=20, sample_dt=1e-3),
traffic=TrafficConfig(
num_vehicles=120,
num_pedestrians=20,
turn_probability=0.1,
vehicle_speed_range=(0 / 3.6, 20 / 3.6),
),
grid=GridConfig(road_width=2.0, road_center_spacing=8.0),
output_dir=Path("examples/data"),
full_output_dir=Path("examples/full_data"),
figures_dir=Path("examples/figs/newyork"),
scenarios_dir=Path("deepmimo_scenarios"),
deepmimo_max_paths=25,
)
generator = DynamicScenarioGenerator(config)
dataset = generator.generate(overwrite=True).payload
From the returned payload you can immediately explore both the continuous tensors and the discrete metadata captured at each grid point:
ue_idx = 10
channel_cont = dataset["channel_cont"][ue_idx]
channel_disc = dataset["discrete"]["channel"][ue_idx]
angle_disc = dataset["angle_discrete"][ue_idx]
delay_disc = dataset["delay_discrete"][ue_idx]
doppler_disc = dataset["doppler_vel_discrete"][ue_idx]
vel_disc = dataset["vel_discrete"][ue_idx]
acc_disc = dataset["acc_discrete"][ue_idx]
# Use the shared AngleDelayProcessor utilities for plotting
from LWMTemporal.data.angle_delay import AngleDelayProcessor, AngleDelayConfig
from examples.ad_temporal_evolutiton import configure_style, pick_bins, plot_curves
configure_style()
proc = AngleDelayProcessor(AngleDelayConfig(keep_percentage=0.25))
ad_trim = proc.truncate_delay_bins(proc.forward(channel_cont))[0]
pick_list = pick_bins(ad_trim, k=3, coords=None)
plot_curves(ad_trim, pick_list, Path("examples/data/figs/ad_curves.png"), title="Continuous vs Discrete")
In this workflow:
channel_contholds the interpolated continuous channel sequence;channel_disccontains the original DeepMIMO channels sampled at the discrete grid points.angle_discrete,delay_discrete, anddoppler_vel_discretelist per-path AoA/AoD delays and Doppler values for each discrete time step.vel_discrete/acc_discretegive the per-sample speed and acceleration derived from the discrete trajectory geometry.- The AngleDelay processor converts any channel tensor (continuous or discrete) into the truncated angle-delay domain, after which the plotting utilities can highlight temporal evolution and GIF exporters can visualize the heatmaps.
Use this section as a drop-in reference when building your own notebooks or scripts around the generator results; it covers dataset construction, metadata inspection, and angle-delay visualization end-to-end.
6. Output Structure
payload (full metadata) includes:
scenario: scenario name.channel_cont/channel: complex array of shape[n_tracks, time_steps, n_tx_ant, n_subcarriers].pos_cont,vel_cont,acc_cont: continuous motion sequences.doppler_vel_cont,angle_cont,delay_cont: lists of per-path values aligned withchannel_cont.pos_step_xy: norm of XY displacement between samples (useful for sanity checks of speed).index_discrete: DeepMIMO user indices visited by each discrete trajectory.channel_discrete: the raw DeepMIMO channels gathered at the discrete user indices.pos_discrete: original polylines before continuous sampling.angle_discrete,delay_discrete,doppler_vel_discrete: per-path metadata matching each discrete time step.vel_discrete,acc_discrete: per-sample speed and acceleration derived from the discrete trajectories.continuous: dict mirroring the continuous keys (channel,pos,vel,acc,doppler_vel,angle,delay).discrete: dict exposingindex,channel,pos,angle,delay,doppler_vel,vel, andaccfor the discrete trajectories.grid_step,sample_dt,car_speed_range, etc.: metadata from configuration.
The slim channel file (in examples/data) typically contains just the channel tensor (channel_cont).
7. Trajectory Behaviour and Interpolation Details
- Road graph: derived from DeepMIMO user coordinates; nodes represent potential road points, edges connect neighbors at roughly the inferred grid spacing.
- Turns: only attempted at points flagged as intersections (overlap between vertical and horizontal road bands) with probability
turn_probability. Otherwise vehicles continue straight if possible. - Dead ends: if a walk runs out of valid neighbors, that trajectory attempt is discarded and a new start is sampled instead of padding or stalling.
- Sampling abstraction:
sample_continuous_along_polylineuses the per-sample speed profile to integrate distances; continuous positions stay on the shape of the discrete trajectory. - Ray interpolation: linear blend of power/ToA/angles/phase with path selection based on strongest rays. Doppler is tied to the instantaneous speed and segment direction.
- Acceleration: computed via finite difference of successive velocities and stored in
acc_cont.
8. Visualization and Plot Styling
- LoS/NLoS/inactive users are plotted with a subdued commercial palette.
- Road graph nodes appear as faint grey dots.
- Vehicle trajectories are solid lines with start markers; pedestrian trajectories are dashed lines. Start markers are white circles with colored edges.
- Only the continuous sampled positions are plotted (no discrete fallback), so the dot trails match the actual motion.
- Figure is saved with
bbox_inches="tight", pad_inches=0.1to minimize whitespace.
9. Extending the Pipeline
Potential customization points:
- Road inference logic (e.g., apply map data instead of inferred spacing).
- Speed profile rules (longer or non-linear ramps).
- Interpolation strategy (e.g., weighting by path lengths or angular similarity).
- Channel synthesis (switch to time-domain or multi-antenna Rx configurations).
- Visualization (brand colors, overlays, additional annotations).
The codebase keeps these components modular to ease experimentation.
10. Troubleshooting
- Scenario download prompts: Accept (
y) when asked, or manually place the downloaded folder indeepmimo_scenarios. - Sparse road nodes / weird turns: Adjust
--road-width/--road-spacing, increase--max-attempts, or supply--grid-step. - Channels not changing with
sample_dt: Ensure the scenario is long enough; if the road segments are too short, the slowdown will trigger early. - Doppler checks: Access
dataset["doppler_vel_cont"]to verify per-path Doppler values; they should reflect the instantaneous speed profile. - Visualization tweaks:
_save_environment_plotcan be edited to change DPI, color scheme, or annotation style.
This guide should provide enough detail to understand and extend the dynamic scenario generator. For further questions or customizations, reach out in the project discussions or open an issue.