2026-05-14 09:41:00 +08:00
|
|
|
import numpy as np
|
2026-05-14 13:59:45 +08:00
|
|
|
from fastapi import HTTPException
|
2026-05-14 09:41:00 +08:00
|
|
|
from scipy import signal
|
|
|
|
|
|
|
|
|
|
from .config import BODE_MAX_MULTIPLIER, BODE_POINTS
|
2026-05-14 13:59:45 +08:00
|
|
|
from .schemas import BodeCompareParams, BodeParams
|
2026-05-14 09:41:00 +08:00
|
|
|
from .validation import require_finite, validate_coefficients
|
|
|
|
|
|
|
|
|
|
|
2026-05-14 13:59:45 +08:00
|
|
|
def _bode_series(b_values, a_values, f_eval, fs_val):
|
|
|
|
|
b_vals = validate_coefficients(b_values, "b")
|
|
|
|
|
a_vals = validate_coefficients(a_values, "a")
|
2026-05-14 09:41:00 +08:00
|
|
|
_, h = signal.freqz(b_vals, a_vals, worN=f_eval, fs=fs_val)
|
2026-05-14 13:59:45 +08:00
|
|
|
return {
|
|
|
|
|
"mag": (20 * np.log10(np.abs(h) + 1e-12)).tolist(),
|
|
|
|
|
"phase": np.angle(h, deg=True).tolist(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _frequency_axis(fs):
|
|
|
|
|
fs_val = require_finite(fs, "fs")
|
|
|
|
|
f_min = fs_val / 50000.0
|
|
|
|
|
f_max = fs_val * BODE_MAX_MULTIPLIER
|
|
|
|
|
return fs_val, np.logspace(np.log10(f_min), np.log10(f_max), BODE_POINTS)
|
|
|
|
|
|
2026-05-14 09:41:00 +08:00
|
|
|
|
2026-05-14 13:59:45 +08:00
|
|
|
def calculate_bode_response(params: BodeParams):
|
|
|
|
|
fs_val, f_eval = _frequency_axis(params.fs)
|
|
|
|
|
series = _bode_series(params.b, params.a, f_eval, fs_val)
|
2026-05-14 09:41:00 +08:00
|
|
|
return {
|
|
|
|
|
"freq": f_eval.tolist(),
|
2026-05-14 13:59:45 +08:00
|
|
|
"mag": series["mag"],
|
|
|
|
|
"phase": series["phase"],
|
2026-05-14 09:41:00 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-14 13:59:45 +08:00
|
|
|
|
|
|
|
|
def calculate_bode_compare_response(params: BodeCompareParams):
|
|
|
|
|
fs_val, f_eval = _frequency_axis(params.ideal.fs)
|
|
|
|
|
fixed_fs_val = require_finite(params.fixed.fs, "fixed.fs")
|
|
|
|
|
if not np.isclose(fixed_fs_val, fs_val):
|
|
|
|
|
raise HTTPException(status_code=400, detail="ideal 與 fixed 的 fs 必須相同")
|
|
|
|
|
|
|
|
|
|
ideal = _bode_series(params.ideal.b, params.ideal.a, f_eval, fs_val)
|
|
|
|
|
fixed = _bode_series(params.fixed.b, params.fixed.a, f_eval, fs_val)
|
|
|
|
|
return {
|
|
|
|
|
"freq": f_eval.tolist(),
|
|
|
|
|
"ideal": ideal,
|
|
|
|
|
"fixed": fixed,
|
|
|
|
|
}
|