Rename package from mcp-ltspice/mcp_ltspice to mcltspice throughout: source directory, imports, pyproject.toml, tests, and README. Remove startup banner prints from main() since FastMCP handles its own banner and stdout is the MCP JSON-RPC transport. Point repo URL at git.supported.systems/MCP/mcltspice.
124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
"""Tests for power_analysis module: average power, efficiency, power factor."""
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from mcltspice.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
|