102 lines
3.8 KiB
Python
102 lines
3.8 KiB
Python
"""Tests for raw_parser module: run boundaries, variable lookup, run slicing."""
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from mcp_ltspice.raw_parser import RawFile, Variable, _detect_run_boundaries
|
|
|
|
|
|
class TestDetectRunBoundaries:
|
|
def test_single_run(self):
|
|
"""Monotonically increasing time -> single run starting at 0."""
|
|
x = np.linspace(0, 1e-3, 500)
|
|
boundaries = _detect_run_boundaries(x)
|
|
assert boundaries == [0]
|
|
|
|
def test_multi_run(self):
|
|
"""Three runs: time resets to near-zero at each boundary."""
|
|
run1 = np.linspace(0, 1e-3, 100)
|
|
run2 = np.linspace(0, 1e-3, 100)
|
|
run3 = np.linspace(0, 1e-3, 100)
|
|
x = np.concatenate([run1, run2, run3])
|
|
boundaries = _detect_run_boundaries(x)
|
|
assert len(boundaries) == 3
|
|
assert boundaries[0] == 0
|
|
assert boundaries[1] == 100
|
|
assert boundaries[2] == 200
|
|
|
|
def test_complex_ac(self):
|
|
"""AC analysis with complex frequency axis that resets."""
|
|
run1 = np.logspace(0, 6, 50).astype(np.complex128)
|
|
run2 = np.logspace(0, 6, 50).astype(np.complex128)
|
|
x = np.concatenate([run1, run2])
|
|
boundaries = _detect_run_boundaries(x)
|
|
assert len(boundaries) == 2
|
|
assert boundaries[0] == 0
|
|
assert boundaries[1] == 50
|
|
|
|
def test_single_point(self):
|
|
"""Single data point -> one run."""
|
|
boundaries = _detect_run_boundaries(np.array([0.0]))
|
|
assert boundaries == [0]
|
|
|
|
|
|
class TestRawFileGetVariable:
|
|
def test_exact_match(self, mock_rawfile):
|
|
"""Exact name match returns correct data."""
|
|
result = mock_rawfile.get_variable("V(out)")
|
|
assert result is not None
|
|
assert len(result) == mock_rawfile.points
|
|
|
|
def test_case_insensitive(self, mock_rawfile):
|
|
"""Variable lookup is case-insensitive (partial match)."""
|
|
result = mock_rawfile.get_variable("v(out)")
|
|
assert result is not None
|
|
|
|
def test_partial_match(self, mock_rawfile):
|
|
"""Substring match should work: 'out' matches 'V(out)'."""
|
|
result = mock_rawfile.get_variable("out")
|
|
assert result is not None
|
|
|
|
def test_missing_variable(self, mock_rawfile):
|
|
"""Non-existent variable returns None."""
|
|
result = mock_rawfile.get_variable("V(nonexistent)")
|
|
assert result is None
|
|
|
|
def test_get_time(self, mock_rawfile):
|
|
result = mock_rawfile.get_time()
|
|
assert result is not None
|
|
assert len(result) == mock_rawfile.points
|
|
|
|
|
|
class TestRawFileRunData:
|
|
def test_get_run_data_slicing(self, mock_rawfile_stepped):
|
|
"""Extracting a single run produces correct point count."""
|
|
run0 = mock_rawfile_stepped.get_run_data(0)
|
|
assert run0.points == 100
|
|
assert run0.n_runs == 1
|
|
assert run0.is_stepped is False
|
|
|
|
def test_get_run_data_values(self, mock_rawfile_stepped):
|
|
"""Each run has the expected amplitude scaling."""
|
|
for i in range(3):
|
|
run = mock_rawfile_stepped.get_run_data(i)
|
|
sig = run.get_variable("V(out)")
|
|
# Peak amplitude should be approximately (i+1)
|
|
assert float(np.max(np.abs(sig))) == pytest.approx(i + 1, rel=0.1)
|
|
|
|
def test_is_stepped(self, mock_rawfile_stepped, mock_rawfile):
|
|
assert mock_rawfile_stepped.is_stepped is True
|
|
assert mock_rawfile.is_stepped is False
|
|
|
|
def test_get_variable_with_run(self, mock_rawfile_stepped):
|
|
"""get_variable with run= parameter slices correctly."""
|
|
v_run1 = mock_rawfile_stepped.get_variable("V(out)", run=1)
|
|
assert v_run1 is not None
|
|
assert len(v_run1) == 100
|
|
|
|
def test_non_stepped_get_run_data(self, mock_rawfile):
|
|
"""Getting run data from non-stepped file returns self."""
|
|
run = mock_rawfile.get_run_data(0)
|
|
assert run.points == mock_rawfile.points
|