Root cause: test-runner was giving overly optimistic results due to: 1. Context bias - knew the implementation, tended to defend it 2. No actual visual comparison - just wrote 'ACCEPTABLE' without looking 3. No structural validation - accepted 35x scale differences as 'acceptable' Solution: - New result-verifier agent that performs blind visual comparison - Strict pass/fail criteria for structural validation - Updated test-runner to use result-verifier for each figure - Clear guidelines: structural mismatches = FAIL, not ACCEPTABLE Test result: verifier correctly identified Fig3 as FAIL with 7 specific issues: - Wrong X-axis variable (channels vs power) - Wrong Y-axis scale (5x difference) - Wrong curve count (5 vs 4) - etc.
90 lines
2.6 KiB
Python
90 lines
2.6 KiB
Python
"""
|
|
src/models/environment.py
|
|
|
|
Implements Module 1: Environment & Channel Simulator
|
|
"""
|
|
|
|
import numpy as np
|
|
from typing import NamedTuple, Tuple
|
|
|
|
|
|
class EnvironmentConfig(NamedTuple):
|
|
"""Configuration for the channel simulator."""
|
|
|
|
num_users: int = 10
|
|
num_channels: int = 10
|
|
bandwidth: float = 1e6 # Hz, 1 MHz per channel
|
|
radius: float = 0.5 # km, cell radius
|
|
shadow_fading_std: float = 6.0 # dB
|
|
noise_psd_dbm: float = -174.0 # dBm/Hz
|
|
|
|
|
|
class ChannelSimulator:
|
|
"""
|
|
Simulates the wireless channel environment.
|
|
|
|
Paper Reference:
|
|
- Pathloss: 128.1 + 37.6 lg[d(km)] dB
|
|
- Shadow fading: 6 dB
|
|
"""
|
|
|
|
def __init__(self, config: EnvironmentConfig):
|
|
self.config = config
|
|
|
|
def _calculate_pathloss(self, distances: np.ndarray) -> np.ndarray:
|
|
"""Calculate pathloss for given distances in km."""
|
|
return 128.1 + 37.6 * np.log10(distances)
|
|
|
|
def generate_channels(
|
|
self, transmit_power_dbm: float
|
|
) -> Tuple[np.ndarray, np.ndarray]:
|
|
"""
|
|
Generate channel conditions (SNR) for all users and channels.
|
|
|
|
Args:
|
|
transmit_power_dbm: Transmit power in dBm
|
|
|
|
Returns:
|
|
Tuple of (snr_db, snr_linear) with shape (num_users, num_channels)
|
|
"""
|
|
N, M = self.config.num_users, self.config.num_channels
|
|
|
|
# 1. Distances (randomly distributed between 0.05km and cell radius)
|
|
min_dist = 0.05
|
|
distances = np.random.uniform(min_dist, self.config.radius, size=N)
|
|
|
|
# 2. Pathloss
|
|
path_loss_db = self._calculate_pathloss(distances)
|
|
|
|
# 3. Shadow fading
|
|
shadowing_db = np.random.normal(0, self.config.shadow_fading_std, size=N)
|
|
|
|
# 4. Total large scale fading (dB)
|
|
large_scale_db = path_loss_db + shadowing_db
|
|
|
|
# 5. Rayleigh fading (small scale)
|
|
# Power of Rayleigh follows exponential distribution (mean=1)
|
|
small_scale_power = np.random.exponential(1.0, size=(N, M))
|
|
small_scale_db = 10 * np.log10(small_scale_power)
|
|
|
|
# 6. Noise power
|
|
# Noise = PSD (dBm/Hz) + 10*log10(BW)
|
|
noise_power_dbm = self.config.noise_psd_dbm + 10 * np.log10(
|
|
self.config.bandwidth
|
|
)
|
|
|
|
# 7. Calculate SNR
|
|
# SNR(dB) = Pt(dBm) - LargeScale(dB) + SmallScale(dB) - Noise(dBm)
|
|
snr_db = np.zeros((N, M))
|
|
for n in range(N):
|
|
snr_db[n, :] = (
|
|
transmit_power_dbm
|
|
- large_scale_db[n]
|
|
+ small_scale_db[n, :]
|
|
- noise_power_dbm
|
|
)
|
|
|
|
snr_linear = 10 ** (snr_db / 10)
|
|
|
|
return snr_db, snr_linear
|