"""Source beam pattern ``.sbp`` reader (port of Matlab ``readpat.m``)."""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
[docs]
@dataclass(frozen=True)
class BeamPatternRead:
"""``power_linear`` is ``10 ** (dB / 20)`` per Matlab ``readpat``."""
n_points: int
angle_deg: np.ndarray
power_linear: np.ndarray
[docs]
def read_beam_pattern_bytes(data: bytes) -> BeamPatternRead:
"""Parse ``.sbp`` bytes: count ``N``, then ``N`` rows of ``angle_deg``, ``power_dB``."""
text = data.decode("utf-8", errors="replace")
lines = [
ln.strip() for ln in text.splitlines() if ln.strip() and not ln.strip().startswith("!")
]
if not lines:
raise ValueError("empty .sbp file")
n = int(lines[0].split()[0])
if len(lines) < 1 + n:
raise ValueError(f".sbp: expected {n} data rows after count")
ang = np.empty(n, dtype=np.float64)
pdb = np.empty(n, dtype=np.float64)
for i in range(n):
parts = lines[1 + i].replace(",", " ").split()
if len(parts) < 2:
raise ValueError(f".sbp row {i + 1}: expected 2 floats")
ang[i] = float(parts[0])
pdb[i] = float(parts[1])
plin = np.power(10.0, pdb / 20.0)
return BeamPatternRead(n_points=n, angle_deg=ang, power_linear=plin)
[docs]
def omni_beam_pattern() -> BeamPatternRead:
"""Omni-directional default when Matlab ``SBP ~= '*'`` (two endpoints, 0 dB)."""
ang = np.array([-180.0, 180.0], dtype=np.float64)
pdb = np.array([0.0, 0.0], dtype=np.float64)
plin = np.power(10.0, pdb / 20.0)
return BeamPatternRead(n_points=2, angle_deg=ang, power_linear=plin)