Phase 3 features bringing the server to 27 tools: - Stepped/multi-run .raw file parsing (.step, .mc, .temp) - Stability analysis (gain/phase margin from AC loop gain) - Power analysis (average, RMS, efficiency, power factor) - Safe waveform expression evaluator (recursive-descent parser) - Component value optimizer (binary search + coordinate descent) - Batch simulation: parameter sweep, temperature sweep, Monte Carlo - .asc schematic generation from templates (RC filter, divider, inverting amp) - Touchstone .s1p/.s2p/.snp S-parameter file parsing - 7 new netlist templates (diff amp, common emitter, buck, LDO, oscillator, H-bridge) - Full ruff lint and format compliance across all modules
197 lines
5.3 KiB
Python
197 lines
5.3 KiB
Python
"""Power and efficiency calculations from simulation data."""
|
|
|
|
import numpy as np
|
|
|
|
# np.trapz was renamed to np.trapezoid in numpy 2.0
|
|
_trapz = getattr(np, "trapezoid", getattr(np, "trapz", None))
|
|
|
|
|
|
def compute_instantaneous_power(voltage: np.ndarray, current: np.ndarray) -> np.ndarray:
|
|
"""Element-wise power: P(t) = V(t) * I(t).
|
|
|
|
Args:
|
|
voltage: Voltage waveform array
|
|
current: Current waveform array
|
|
|
|
Returns:
|
|
Instantaneous power array (same length as inputs)
|
|
"""
|
|
return np.real(voltage) * np.real(current)
|
|
|
|
|
|
def compute_average_power(time: np.ndarray, voltage: np.ndarray, current: np.ndarray) -> float:
|
|
"""Time-averaged power: P_avg = (1/T) * integral(V*I dt).
|
|
|
|
Uses trapezoidal integration over the full time span.
|
|
|
|
Args:
|
|
time: Time array in seconds
|
|
voltage: Voltage waveform array
|
|
current: Current waveform array
|
|
|
|
Returns:
|
|
Average power in watts
|
|
"""
|
|
t = np.real(time)
|
|
duration = t[-1] - t[0]
|
|
|
|
if len(t) < 2 or duration <= 0:
|
|
return 0.0
|
|
|
|
p_inst = np.real(voltage) * np.real(current)
|
|
return float(_trapz(p_inst, t) / duration)
|
|
|
|
|
|
def compute_efficiency(
|
|
time: np.ndarray,
|
|
input_voltage: np.ndarray,
|
|
input_current: np.ndarray,
|
|
output_voltage: np.ndarray,
|
|
output_current: np.ndarray,
|
|
) -> dict:
|
|
"""Compute power conversion efficiency.
|
|
|
|
Args:
|
|
time: Time array in seconds
|
|
input_voltage: Input voltage waveform
|
|
input_current: Input current waveform
|
|
output_voltage: Output voltage waveform
|
|
output_current: Output current waveform
|
|
|
|
Returns:
|
|
Dict with efficiency_percent, input_power_watts,
|
|
output_power_watts, power_dissipated_watts
|
|
"""
|
|
p_in = compute_average_power(time, input_voltage, input_current)
|
|
p_out = compute_average_power(time, output_voltage, output_current)
|
|
p_dissipated = p_in - p_out
|
|
|
|
if abs(p_in) < 1e-15:
|
|
efficiency = 0.0
|
|
else:
|
|
efficiency = (p_out / p_in) * 100.0
|
|
|
|
return {
|
|
"efficiency_percent": efficiency,
|
|
"input_power_watts": p_in,
|
|
"output_power_watts": p_out,
|
|
"power_dissipated_watts": p_dissipated,
|
|
}
|
|
|
|
|
|
def compute_power_spectrum(
|
|
time: np.ndarray,
|
|
voltage: np.ndarray,
|
|
current: np.ndarray,
|
|
max_harmonics: int = 20,
|
|
) -> dict:
|
|
"""FFT of instantaneous power to identify ripple frequencies.
|
|
|
|
Args:
|
|
time: Time array in seconds
|
|
voltage: Voltage waveform array
|
|
current: Current waveform array
|
|
max_harmonics: Maximum number of frequency bins to return
|
|
|
|
Returns:
|
|
Dict with frequencies, power_magnitudes, dominant_freq, dc_power
|
|
"""
|
|
t = np.real(time)
|
|
|
|
if len(t) < 2:
|
|
return {
|
|
"frequencies": [],
|
|
"power_magnitudes": [],
|
|
"dominant_freq": 0.0,
|
|
"dc_power": 0.0,
|
|
}
|
|
|
|
dt = (t[-1] - t[0]) / (len(t) - 1)
|
|
if dt <= 0:
|
|
return {
|
|
"frequencies": [],
|
|
"power_magnitudes": [],
|
|
"dominant_freq": 0.0,
|
|
"dc_power": float(np.mean(np.real(voltage) * np.real(current))),
|
|
}
|
|
|
|
p_inst = np.real(voltage) * np.real(current)
|
|
n = len(p_inst)
|
|
|
|
spectrum = np.fft.rfft(p_inst)
|
|
freqs = np.fft.rfftfreq(n, d=dt)
|
|
magnitudes = np.abs(spectrum) * 2.0 / n
|
|
magnitudes[0] /= 2.0 # DC component correction
|
|
|
|
dc_power = float(magnitudes[0])
|
|
|
|
# Dominant AC frequency (largest non-DC bin)
|
|
if len(magnitudes) > 1:
|
|
dominant_idx = int(np.argmax(magnitudes[1:])) + 1
|
|
dominant_freq = float(freqs[dominant_idx])
|
|
else:
|
|
dominant_freq = 0.0
|
|
|
|
# Trim to requested harmonics (plus DC)
|
|
limit = min(max_harmonics + 1, len(freqs))
|
|
freqs = freqs[:limit]
|
|
magnitudes = magnitudes[:limit]
|
|
|
|
return {
|
|
"frequencies": freqs.tolist(),
|
|
"power_magnitudes": magnitudes.tolist(),
|
|
"dominant_freq": dominant_freq,
|
|
"dc_power": dc_power,
|
|
}
|
|
|
|
|
|
def compute_power_metrics(time: np.ndarray, voltage: np.ndarray, current: np.ndarray) -> dict:
|
|
"""Comprehensive power report for a voltage/current pair.
|
|
|
|
Power factor here is defined as the ratio of real (average) power
|
|
to apparent power (Vrms * Irms).
|
|
|
|
Args:
|
|
time: Time array in seconds
|
|
voltage: Voltage waveform array
|
|
current: Current waveform array
|
|
|
|
Returns:
|
|
Dict with avg_power, rms_power, peak_power, min_power, power_factor
|
|
"""
|
|
v = np.real(voltage)
|
|
i = np.real(current)
|
|
p_inst = v * i
|
|
|
|
if len(p_inst) == 0:
|
|
return {
|
|
"avg_power": 0.0,
|
|
"rms_power": 0.0,
|
|
"peak_power": 0.0,
|
|
"min_power": 0.0,
|
|
"power_factor": 0.0,
|
|
}
|
|
|
|
avg_power = compute_average_power(time, voltage, current)
|
|
rms_power = float(np.sqrt(np.mean(p_inst**2)))
|
|
peak_power = float(np.max(p_inst))
|
|
min_power = float(np.min(p_inst))
|
|
|
|
# Power factor = avg power / apparent power (Vrms * Irms)
|
|
v_rms = float(np.sqrt(np.mean(v**2)))
|
|
i_rms = float(np.sqrt(np.mean(i**2)))
|
|
apparent = v_rms * i_rms
|
|
|
|
if apparent < 1e-15:
|
|
power_factor = 0.0
|
|
else:
|
|
power_factor = avg_power / apparent
|
|
|
|
return {
|
|
"avg_power": avg_power,
|
|
"rms_power": rms_power,
|
|
"peak_power": peak_power,
|
|
"min_power": min_power,
|
|
"power_factor": power_factor,
|
|
}
|