mcltspice/tests/test_power_analysis.py
2026-02-10 23:35:53 -07:00

124 lines
4.2 KiB
Python

"""Tests for power_analysis module: average power, efficiency, power factor."""
import numpy as np
import pytest
from mcp_ltspice.power_analysis import (
compute_average_power,
compute_efficiency,
compute_instantaneous_power,
compute_power_metrics,
)
class TestComputeAveragePower:
def test_dc_power(self):
"""DC: P = V * I exactly."""
n = 1000
t = np.linspace(0, 1.0, n)
v = np.full(n, 5.0)
i = np.full(n, 2.0)
p = compute_average_power(t, v, i)
assert p == pytest.approx(10.0, rel=1e-6)
def test_ac_in_phase(self):
"""In-phase AC: P_avg = Vpk*Ipk/2."""
n = 10000
t = np.linspace(0, 0.01, n, endpoint=False) # 1 full period at 100 Hz
freq = 100.0
Vpk = 10.0
Ipk = 2.0
v = Vpk * np.sin(2 * np.pi * freq * t)
i = Ipk * np.sin(2 * np.pi * freq * t)
p = compute_average_power(t, v, i)
expected = Vpk * Ipk / 2.0 # = Vrms * Irms
assert p == pytest.approx(expected, rel=0.02)
def test_ac_quadrature(self):
"""90-degree phase shift: P_avg ~ 0 (reactive power only)."""
n = 10000
t = np.linspace(0, 0.01, n, endpoint=False)
freq = 100.0
v = np.sin(2 * np.pi * freq * t)
i = np.cos(2 * np.pi * freq * t) # 90 deg shifted
p = compute_average_power(t, v, i)
assert p == pytest.approx(0.0, abs=0.01)
def test_short_signal(self):
assert compute_average_power(np.array([0.0]), np.array([5.0]), np.array([2.0])) == 0.0
class TestComputeEfficiency:
def test_known_efficiency(self):
"""Input 10W, output 8W -> 80% efficiency."""
n = 1000
t = np.linspace(0, 1.0, n)
vin = np.full(n, 10.0)
iin = np.full(n, 1.0) # 10W input
vout = np.full(n, 8.0)
iout = np.full(n, 1.0) # 8W output
result = compute_efficiency(t, vin, iin, vout, iout)
assert result["efficiency_percent"] == pytest.approx(80.0, rel=0.01)
assert result["input_power_watts"] == pytest.approx(10.0, rel=0.01)
assert result["output_power_watts"] == pytest.approx(8.0, rel=0.01)
assert result["power_dissipated_watts"] == pytest.approx(2.0, rel=0.01)
def test_zero_input_power(self):
"""Zero input -> 0% efficiency (avoid division by zero)."""
n = 100
t = np.linspace(0, 1.0, n)
zeros = np.zeros(n)
result = compute_efficiency(t, zeros, zeros, zeros, zeros)
assert result["efficiency_percent"] == 0.0
class TestPowerFactor:
def test_dc_power_factor(self):
"""DC signals (in phase) should have PF = 1.0."""
n = 1000
t = np.linspace(0, 1.0, n)
v = np.full(n, 5.0)
i = np.full(n, 2.0)
result = compute_power_metrics(t, v, i)
assert result["power_factor"] == pytest.approx(1.0, rel=0.01)
def test_ac_in_phase_power_factor(self):
"""In-phase AC should have PF ~ 1.0."""
n = 10000
t = np.linspace(0, 0.01, n, endpoint=False)
freq = 100.0
v = np.sin(2 * np.pi * freq * t)
i = np.sin(2 * np.pi * freq * t)
result = compute_power_metrics(t, v, i)
assert result["power_factor"] == pytest.approx(1.0, rel=0.05)
def test_ac_quadrature_power_factor(self):
"""90-degree phase shift -> PF ~ 0."""
n = 10000
t = np.linspace(0, 0.01, n, endpoint=False)
freq = 100.0
v = np.sin(2 * np.pi * freq * t)
i = np.cos(2 * np.pi * freq * t)
result = compute_power_metrics(t, v, i)
assert result["power_factor"] == pytest.approx(0.0, abs=0.05)
def test_empty_signals(self):
result = compute_power_metrics(np.array([]), np.array([]), np.array([]))
assert result["power_factor"] == 0.0
class TestInstantaneousPower:
def test_element_wise(self):
v = np.array([1.0, 2.0, 3.0])
i = np.array([0.5, 1.0, 1.5])
p = compute_instantaneous_power(v, i)
np.testing.assert_array_almost_equal(p, [0.5, 2.0, 4.5])
def test_complex_uses_real(self):
"""Should use real parts only."""
v = np.array([3.0 + 4j])
i = np.array([2.0 + 1j])
p = compute_instantaneous_power(v, i)
assert p[0] == pytest.approx(6.0) # 3 * 2