17 tests: round_sig/compact_list rounding, downsample_indices stride vs peak-preserving (a narrow spike must survive), and extract_waveform/ analyze_signal across AC/transient/x-range/error/all-analysis paths.
78 lines
2.9 KiB
Python
78 lines
2.9 KiB
Python
"""Tests for extract_waveform / analyze_signal -- RawFile queries sans simulator."""
|
|
|
|
import numpy as np
|
|
|
|
from mcltspice.raw_parser import RawFile, Variable
|
|
from mcltspice.waveform_query import analyze_signal, extract_waveform
|
|
|
|
|
|
def _ac_raw() -> RawFile:
|
|
freq = np.logspace(0, 6, 300)
|
|
hout = 1.0 / (1.0 + 1j * (freq / 1000.0))
|
|
data = np.array([freq.astype(complex), hout])
|
|
return RawFile(
|
|
title="", date="", plotname="AC Analysis", flags=["complex"],
|
|
variables=[Variable(0, "frequency", "frequency"), Variable(1, "V(out)", "voltage")],
|
|
points=len(freq), data=data,
|
|
)
|
|
|
|
|
|
def _tran_raw() -> RawFile:
|
|
t = np.linspace(0, 0.01, 800)
|
|
v = 2.0 * np.sin(2 * np.pi * 1000 * t)
|
|
return RawFile(
|
|
title="", date="", plotname="Transient Analysis", flags=["real"],
|
|
variables=[Variable(0, "time", "time"), Variable(1, "V(out)", "voltage")],
|
|
points=len(t), data=np.array([t, v]),
|
|
)
|
|
|
|
|
|
class TestExtractWaveform:
|
|
def test_transient_values(self):
|
|
r = extract_waveform(_tran_raw(), ["V(out)"], max_points=100)
|
|
assert r["x_axis_name"] == "time"
|
|
assert r["returned_points"] <= 100
|
|
assert "values" in r["signals"]["V(out)"]
|
|
|
|
def test_ac_gives_magnitude_and_phase(self):
|
|
r = extract_waveform(_ac_raw(), ["V(out)"])
|
|
assert r["x_axis_name"] == "frequency"
|
|
sig = r["signals"]["V(out)"]
|
|
assert "magnitude_db" in sig and "phase_degrees" in sig
|
|
|
|
def test_x_range_filter(self):
|
|
full = extract_waveform(_tran_raw(), ["V(out)"], max_points=10000)["returned_points"]
|
|
clipped = extract_waveform(
|
|
_tran_raw(), ["V(out)"], max_points=10000, x_min=0.002, x_max=0.004
|
|
)["returned_points"]
|
|
assert clipped < full
|
|
|
|
def test_empty_range_error(self):
|
|
r = extract_waveform(_tran_raw(), ["V(out)"], x_min=1e12)
|
|
assert "error" in r and "range" in r["error"]
|
|
|
|
def test_run_on_non_stepped_error(self):
|
|
r = extract_waveform(_tran_raw(), ["V(out)"], run=2)
|
|
assert "error" in r and "stepped" in r["error"]
|
|
|
|
|
|
class TestAnalyzeSignal:
|
|
def test_rms_and_peak_to_peak(self):
|
|
r = analyze_signal(_tran_raw(), "V(out)", ["rms", "peak_to_peak"])
|
|
assert r["signal"] == "V(out)"
|
|
assert abs(r["rms"] - 2.0 / np.sqrt(2)) < 0.05 # RMS of a 2 V amplitude sine
|
|
assert abs(r["peak_to_peak"]["peak_to_peak"] - 4.0) < 0.05
|
|
|
|
def test_fft_and_thd_present(self):
|
|
r = analyze_signal(_tran_raw(), "V(out)", ["fft", "thd"])
|
|
assert "fft" in r and "thd" in r
|
|
|
|
def test_missing_signal_error(self):
|
|
r = analyze_signal(_tran_raw(), "V(ghost)", ["rms"])
|
|
assert "error" in r and "V(ghost)" in r["error"]
|
|
|
|
def test_complex_signal_uses_magnitude(self):
|
|
# AC (complex) signal: analysis should run on |signal| without raising
|
|
r = analyze_signal(_ac_raw(), "V(out)", ["rms"])
|
|
assert "rms" in r and r["rms"] >= 0
|