"""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