"""Reflection coefficient ``.trc`` / ``.brc`` readers (port of Matlab ``readrc.m`` core I/O)."""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
[docs]
@dataclass(frozen=True)
class ReflectionCoeffRead:
"""Angles in radians (``phi`` converted from degrees as in Matlab)."""
n_points: int
theta: np.ndarray
r: np.ndarray
phi_rad: np.ndarray
[docs]
def read_rc_bytes(data: bytes) -> ReflectionCoeffRead:
"""Parse one **top** (``.trc``) or **bottom** (``.brc``) reflection coefficient file.
Format: integer ``N``, then ``N`` lines of ``theta``, ``R``, ``phi`` with ``phi`` in
**degrees** (converted to radians like Matlab ``readrc``).
"""
text = data.decode("utf-8", errors="replace")
lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
if not lines:
raise ValueError("empty RC file")
n = int(lines[0].split()[0])
if len(lines) < 1 + n:
raise ValueError(f"RC file: expected {n} data rows after count, got {len(lines) - 1}")
theta = np.empty(n, dtype=np.float64)
r = np.empty(n, dtype=np.float64)
phi_deg = np.empty(n, dtype=np.float64)
for i in range(n):
parts = lines[1 + i].replace(",", " ").split()
if len(parts) < 3:
raise ValueError(f"RC row {i + 1}: expected 3 floats, got {parts!r}")
theta[i] = float(parts[0])
r[i] = float(parts[1])
phi_deg[i] = float(parts[2])
phi_rad = (np.pi / 180.0) * phi_deg
if n > 1:
if np.any(np.diff(theta) < -1e-12):
raise ValueError("RC file: theta must be non-decreasing (Matlab validateattributes)")
return ReflectionCoeffRead(n_points=n, theta=theta, r=r, phi_rad=phi_rad)
[docs]
def read_rc_trc_bytes(data: bytes) -> ReflectionCoeffRead:
"""Alias for a **top** ``.trc`` file (same parser as :func:`read_rc_bytes`)."""
return read_rc_bytes(data)
[docs]
def read_rc_brc_bytes(data: bytes) -> ReflectionCoeffRead:
"""Alias for a **bottom** ``.brc`` file (same parser as :func:`read_rc_bytes`)."""
return read_rc_bytes(data)