feat: OVF formula 67

This commit is contained in:
mayge
2025-09-17 02:45:10 -04:00
parent 1b21b940a4
commit e1bc69c6fb
10 changed files with 724 additions and 446 deletions

174
core/orthonormal_basis.py Normal file
View File

@@ -0,0 +1,174 @@
import numpy as np
# ------------------------------------------------------------
# 按论文公式 (9)(10)(11) 生成 MuntzLaguerre 正交有理基 (解析形式)
#
# 给定稳定极点集合 {p_k} (Re(p_k)<0)。论文记法中使用 -a_k其中 Re(a_k)>0。
# 对应关系p_k = -a_k ⇒ a_k = -p_k, Re(a_k)= -Re(p_k) >0
#
# 连续内积意义下(沿 jω 轴积分)这些 φ_k 解析正交。离散频率采样后数值上
# 可能偏离,可再用加权 QR 做数值再正交(可选)。
#
# 公式在“稳定极点 p 表达”下的改写:
# (原) 实极点: φ_p(s) = sqrt(2 Re(a_p)) / (s + a_p) * Π (s - a_i^*)/(s + a_i)
# 变换 a_p = -p ⇒ Re(a_p)= -Re(p) = σ >0 且 (s + a_p) = (s - p)
# 且乘积 (s - a_i^*)/(s + a_i) = (s + p_i^*)/(s - p_i)
# ⇒ φ_p(s) = sqrt(-2 Re(p)) / (s - p) * Π_{i<p} (s + p_i^*)/(s - p_i)
#
# 复对 (p, p*),取 imag(p)>0 的 p 作为首:
# (原) φ_p = sqrt(2 Re(a_p)) (s - |a_p|)/[(s + a_p)(s + a_p^*)] * Π(...)
# (原) φ_{p+1}= sqrt(2 Re(a_p)) (s + |a_p|)/[(s + a_p)(s + a_p^*)] * Π(...)
# 代入 a_p=-p
# Re(a_p)= -Re(p)=σ>0, (s + a_p) = (s - p), (s + a_p^*)=(s - p^*)
# |a_p| = |p|
# 乘积同上 ⇒ Π_{i<p} (s + p_i^*)/(s - p_i)
#
# ⇒ φ_p(s) = sqrt(-2 Re(p)) (s - |p|)/[(s - p)(s - p^*)] * Π_{i<p} (s + p_i^*)/(s - p_i)
# φ_{p^*}(s)= sqrt(-2 Re(p)) (s + |p|)/[(s - p)(s - p^*)] * Π_{i<p} (s + p_i^*)/(s - p_i)
#
# product 递推维护prod_k = Π_{i≤k} (s + p_i^*)/(s - p_i)
# 对复对要顺序乘两次p 与 p*)。
# ------------------------------------------------------------
def generate_laguerre_basis(poles:list|np.ndarray, s: np.ndarray):
basis = np.zeros((len(poles)+1,len(s)),dtype=complex)
product = np.ones(len(s),dtype=complex)
basis[0] = np.ones(len(s),dtype=complex) # φ_0 = 1
i = 0
while i < len(poles):
if np.real(poles[i]) >= 0:
raise ValueError(f"极点必须在左半平面: {poles[i]}")
# 复对首 (正虚部)
if np.iscomplex(poles[i]) and np.imag(poles[i]) > 0:
if i + 1 >= len(poles):
raise ValueError("复极点缺少共轭")
pc = poles[i + 1]
if not np.isclose(pc, np.conj(poles[i])):
raise ValueError("复极点未按 (p, p*) 顺序排列 (正虚部在前)")
sigma = -np.real(poles[i]) # >0
scale = np.sqrt(2 * sigma)
r = np.abs(poles[i])
denom = (s - poles[i]) * (s - pc)
# 两个基函数
phi_p = scale * (s - r) / denom * product
phi_pc = scale * (s + r) / denom * product
# product 先乘 (s + p^*)/(s - p),再乘 (s + p)/(s - p^*)
product = product * (s + pc) / (s - poles[i])
product = product * (s + poles[i]) / (s - pc)
basis[i + 1] = phi_p
basis[i + 2] = phi_pc
i += 2
continue
# 复对次 (负虚部) —— 应该被首元素处理,出现表示顺序错误
if np.iscomplex(poles[i]) and np.imag(poles[i]) < 0:
raise ValueError("检测到负虚部复极点但其共轭尚未处理,请将正虚部成员放在前面。")
# 实极点
sigma = -np.real(poles[i])
if sigma <= 0:
raise ValueError("实极点实部应为负 (稳定)。")
scale = np.sqrt(2 * sigma)
phi = scale / (s - poles[i]) * product
# 更新乘积
product = product * (s + poles[i]) / (s - poles[i])
i += 1
basis[i + 1] = phi
return basis
# class MuntzLaguerreIterator:
# def __init__(self, s: np.ndarray, stable_poles: list | np.ndarray):
# """
# s: 复频率数组 (Nf,), s = j 2π f
# stable_poles: 稳定极点列表 (Re<0). 复共轭对要求正虚部在前 (p, p*).
# """
# self.s = np.asarray(s, dtype=complex)
# self.poles = list(stable_poles)
# self.N = len(self.poles)
# self.k = 0
# # 初始化乘积 Π_{i<p} (s + p_i^*)/(s - p_i)
# self.product = np.ones_like(self.s, dtype=complex)
# def __iter__(self):
# return self
# def __next__(self):
# if self.k >= self.N:
# raise StopIteration
# p = self.poles[self.k]
# if np.real(p) >= 0:
# raise ValueError(f"极点必须在左半平面: {p}")
# # 复对首 (正虚部)
# if np.iscomplex(p) and np.imag(p) > 0:
# if self.k + 1 >= self.N:
# raise ValueError("复极点缺少共轭")
# pc = self.poles[self.k + 1]
# if not np.isclose(pc, np.conj(p)):
# raise ValueError("复极点未按 (p, p*) 顺序排列 (正虚部在前)")
# sigma = -np.real(p) # >0
# scale = np.sqrt(2 * sigma)
# r = np.abs(p)
# denom = (self.s - p) * (self.s - pc)
# # 两个基函数
# phi_p = scale * (self.s - r) / denom * self.product
# phi_pc = scale * (self.s + r) / denom * self.product
# # product 先乘 (s + p^*)/(s - p),再乘 (s + p)/(s - p^*)
# self.product = self.product * (self.s + pc) / (self.s - p)
# self.product = self.product * (self.s + p) / (self.s - pc)
# self.k += 2
# return [phi_p, phi_pc]
# # 复对次 (负虚部) —— 应该被首元素处理,出现表示顺序错误
# if np.iscomplex(p) and np.imag(p) < 0:
# raise ValueError("检测到负虚部复极点但其共轭尚未处理,请将正虚部成员放在前面。")
# # 实极点
# sigma = -np.real(p)
# if sigma <= 0:
# raise ValueError("实极点实部应为负 (稳定)。")
# scale = np.sqrt(2 * sigma)
# phi = scale / (self.s - p) * self.product
# # 更新乘积
# self.product = self.product * (self.s + p) / (self.s - p)
# self.k += 1
# return [phi]
# def generate_muntz_laguerre_basis(s: np.ndarray, init_poles: list | np.ndarray):
# """
# 生成完整基函数列表: [φ_0=1, φ_1, φ_2, ...]
# """
# basis = [np.ones_like(s, dtype=complex)]
# for block in MuntzLaguerreIterator(s, init_poles):
# basis.extend(block)
# return basis
# if __name__ == "__main__":
# # 示例稳定极点 (复对正虚部在前)
# stable_poles = [
# -0.8e9,
# -1.0e9 + 2.5e9j,
# -1.0e9 - 2.5e9j,
# -2.2e9
# ]
# freqs = np.linspace(1e8, 8e9, 400)
# s = 1j * 2 * np.pi * freqs
# basis = generate_muntz_laguerre_basis(s, stable_poles)
# print(f"生成 {len(basis)} 个基函数,{basis}")

View File

@@ -1,250 +1,326 @@
import numpy as np
from dataclasses import dataclass
from typing import List, Sequence, Tuple, Optional
from typing import List, Dict, Tuple
@dataclass
class OPVFConfig:
n_poles: int
max_iter: int = 5
tol: float = 1e-3
add_constraint: bool = True
real_split: bool = True
lambda_reg: float = 0.0
verbose: bool = True
# ---------- 参数正交基 ----------
class ParamOrthoBasis:
def __init__(self, params: np.ndarray, total_degree: int):
# params: (Ns, d)
self.mean = params.mean(0)
self.std = params.std(0) + 1e-15
self.exps = self._gen_exps(params.shape[1], total_degree)
M = self._build_monomial_matrix(params) # (Ns,M)
Q,R = np.linalg.qr(M)
self.Q = Q
self.R = R
def _gen_exps(self, dim, D):
exps=[]
def rec(cur, i, rem):
if i==dim:
exps.append(tuple(cur)); return
for k in range(rem+1):
cur.append(k); rec(cur, i+1, rem-k); cur.pop()
rec([],0,D)
return exps
def _build_monomial_matrix(self, params):
X = (params - self.mean)/self.std
Ns = X.shape[0]
M = np.zeros((Ns, len(self.exps)))
for idx,e in enumerate(self.exps):
v = np.ones(Ns)
for j,p in enumerate(e):
if p: v *= X[:,j]**p
M[:,idx]=v
return M
def eval(self, g: np.ndarray) -> np.ndarray:
x = (g - self.mean)/self.std
mono=[]
for e in self.exps:
val=1.0
for j,p in enumerate(e):
if p: val*= x[j]**p
mono.append(val)
mono=np.array(mono)
# φ = mono @ R^{-1}
return mono @ np.linalg.inv(self.R)
# ---------- 频率正交(有理)基 ----------
class RationalFreqBasis:
def __init__(self, freqs: np.ndarray, poles: np.ndarray):
# 构造原始矩阵 G: 列0 常数 1, 后面 1/(s-a_k)
s = 1j*2*np.pi*freqs
cols=[np.ones_like(s)]
for a in poles:
cols.append(1.0/(s - a))
G = np.vstack(cols).T # (Nf, K+1)
# 加权可加 w_f这里统一 1
Q, R = np.linalg.qr(G)
self.Q = Q # Φ(f) (Nf, K+1) 频率正交基取样
self.R = R # 变换: G = Q R
self.freqs = freqs
def eval(self) -> np.ndarray:
return self.Q # 直接返回已正交基取样 (Nf, K+1)
# ---------- 主模型 ----------
class OrthonormalParametricVF:
# ---------- 1. 生成初始极点(按论文 Re(-a_p) 小、Im 线性分布) ----------
def generate_starting_poles(n_pairs: int, beta_min: float, beta_max: float, alpha_scale: float = 0.01):
"""
H(s, μ) = N(s,μ)/D(s,μ)
N(s,μ)= Σ_k c_k(μ) φ_k(s); D(s,μ)= Σ_k ĉ_k(μ) φ_k(s)
其中 φ_k(s) 是频率正交有理基; c_k(μ), ĉ_k(μ) 在参数正交基上展开:
c_k(μ)= Σ_q C[k,q] ψ_q(μ); ĉ_k(μ)= Σ_q Ctilde[k,q] ψ_q(μ)
仅生成复共轭对: p = -alpha + j beta, p*。
n_pairs: 复对数量 (总极点数 = 2*n_pairs)
beta_min,beta_max: 想要覆盖的虚部范围 (单位: rad/s)
alpha_scale: alpha = alpha_scale * beta (文中 {α_p}=0.01{β_p})
返回: list[complex] (正虚部先, 后跟共轭)
"""
def __init__(self, cfg: OPVFConfig, freq_basis: RationalFreqBasis, param_basis: ParamOrthoBasis):
self.cfg = cfg
self.fb = freq_basis
self.pb = param_basis
Kp1 = self.fb.Q.shape[1]
Qp = self.pb.Q.shape[1]
self.C = np.zeros((Kp1, Qp), dtype=complex) # 分子
self.Ct = np.zeros((Kp1, Qp), dtype=complex) # 分母
# 初始化分母ĉ_0 ≈ 1其余 0
self.Ct[0,0] = 1.0
betas = 2*np.pi*np.linspace(beta_min, beta_max, n_pairs)
poles = []
for b in betas:
alpha = alpha_scale * b
p = -alpha + 1j * b
poles += [p, np.conj(p)]
print(f"生成 {len(poles)} 个初始极点 (复对) {poles}]")
return poles
def _assemble_phi_param(self, params: np.ndarray) -> np.ndarray:
# 返回 (Ns, Qp)
return self.pb.Q
# ---------- 2. MuntzLaguerre 频率基 (与你现有 orthonormal_basis 中一致的核心) ----------
def muntz_laguerre_basis(freqs_hz: np.ndarray, stable_poles: List[complex]) -> np.ndarray:
"""
返回 Φ_ml (Nf, P) 列顺序: φ_0=1, 后续依次 (单极点 / 复对两列)
"""
s = 1j * 2 * np.pi * freqs_hz
basis = [np.ones_like(s, dtype=complex)]
product = np.ones_like(s, dtype=complex)
k = 0
N = len(stable_poles)
while k < N:
p = stable_poles[k]
if np.real(p) >= 0:
raise ValueError("极点需在左半平面")
if np.imag(p) > 0: # 复对首
if k + 1 >= N or not np.isclose(stable_poles[k+1], np.conj(p)):
raise ValueError("复对未配对")
pc = stable_poles[k+1]
sigma = -np.real(p)
scale = np.sqrt(2 * sigma)
r = np.abs(p)
denom = (s - p) * (s - pc)
phi_p = scale * (s - r) / denom * product
phi_pc = scale * (s + r) / denom * product
basis.extend([phi_p, phi_pc])
# update product
product = product * (s + pc)/(s - p) * (s + p)/(s - pc)
k += 2
elif np.imag(p) < 0:
raise ValueError("负虚部复极点必须跟在正虚部之后")
else: # 实极点
sigma = -np.real(p)
scale = np.sqrt(2 * sigma)
phi = scale / (s - p) * product
basis.append(phi)
product = product * (s + p)/(s - p)
k += 1
return np.column_stack(basis) # (Nf, P)
def fit(self, H_samples: List[np.ndarray], params: np.ndarray):
"""
H_samples: list 长度 Ns, 每个 (Nf,) 复
params: (Ns,d)
"""
Φf = self.fb.eval() # (Nf, K+1)
Ns = len(H_samples)
Nf, Kp1 = Φf.shape
Φμ = self._assemble_phi_param(params) # (Ns, Qp)
Qp = Φμ.shape[1]
# ---------- 3. 原始部分分式基 ----------
def raw_partial_fraction_basis(freqs_hz: np.ndarray, stable_poles: List[complex]) -> np.ndarray:
s = 1j * 2 * np.pi * freqs_hz
cols = [np.ones_like(s, dtype=complex)]
for p in stable_poles:
cols.append(1.0 / (s - p))
return np.column_stack(cols) # (Nf, P)
# t=0: 只解 C (式3, D=1)
self._solve_t0(H_samples, Φf, Φμ)
# ---------- 4. 变换矩阵 Φ_ml T = G_raw ----------
def compute_transform_matrix(Phi_ml: np.ndarray, G_raw: np.ndarray, weights: np.ndarray | None = None) -> np.ndarray:
# 加权最小二乘 T = (Φ^H W Φ)^{-1} Φ^H W G
if weights is None:
WPhi = Phi_ml
WG = G_raw
M = Phi_ml.conj().T @ WPhi
RHS = Phi_ml.conj().T @ WG
else:
w = weights[:, None]
M = Phi_ml.conj().T @ (w * Phi_ml)
RHS = Phi_ml.conj().T @ (w * G_raw)
T = np.linalg.solve(M, RHS)
return T # (P,P), Φ T = G
D_prev = self._eval_D(Φf, Φμ) # (Nf, Ns)
# ---------- 5. 参数多项式正交基 ----------
def generate_param_basis(param_matrix: np.ndarray, total_degree: int):
"""
param_matrix: (Ns, d)
返回:
Qμ: (Ns, V) 正交列
Rμ: (V, V) 上三角
exps: 多指数列表
"""
X = param_matrix
Ns, d = X.shape
# 生成多指数
exps=[]
def rec(cur,i,rem):
if i==d:
exps.append(tuple(cur)); return
for k in range(rem+1):
cur.append(k); rec(cur,i+1,rem-k); cur.pop()
rec([],0,total_degree)
V = len(exps)
M = np.zeros((Ns, V))
for idx,e in enumerate(exps):
v = np.ones(Ns)
for j,p in enumerate(e):
if p: v *= X[:,j]**p
M[:,idx]=v
Q,R = np.linalg.qr(M)
return Q,R,exps
for it in range(1, self.cfg.max_iter+1):
A, b = self._build_iter_system(H_samples, Φf, Φμ, D_prev)
if self.cfg.lambda_reg>0:
lam = self.cfg.lambda_reg
A = np.vstack([A, lam*np.eye(A.shape[1])])
b = np.concatenate([b, np.zeros(A.shape[1])])
x, *_ = np.linalg.lstsq(A, b, rcond=None)
self._unpack_iter_solution(x, Kp1, Qp)
D_new = self._eval_D(Φf, Φμ)
rel = np.max(np.abs(D_new-D_prev)/(np.abs(D_prev)+1e-12))
if self.cfg.verbose:
print(f"[OPVF-Iter {it}] rel_change={rel:.3e}")
D_prev = D_new
if rel < self.cfg.tol:
if self.cfg.verbose:
print(f"[OPVF] converged at {it}")
break
return self
# ---------- 6. t=0 构建设计矩阵并解 C ----------
def fit_t0(H_list: List[np.ndarray], Phi_f: np.ndarray, Qμ: np.ndarray) -> np.ndarray:
"""
H_list: 长度 Ns, 每项 (Nf,) 复数
Phi_f: (Nf, P)
Qμ: (Ns, V)
返回: C (P,V)
"""
Ns = len(H_list); Nf,P = Phi_f.shape; V = Qμ.shape[1]
cols = P*V
A = np.zeros((Ns*Nf, cols), dtype=complex)
b = np.zeros(Ns*Nf, dtype=complex)
r=0
for n,Hn in enumerate(H_list):
blk = np.einsum('fp,v->fpv', Phi_f, [n]).reshape(Nf, cols)
A[r:r+Nf,:]=blk
b[r:r+Nf]=Hn
r+=Nf
x, *_ = np.linalg.lstsq(A, b, rcond=None)
C = x.reshape(P, V)
return C
def _solve_t0(self, H_samples, Φf, Φμ):
Ns = len(H_samples); Nf,Kp1 = Φf.shape; Qp = Φμ.shape[1]
# 设计矩阵行数 Ns*Nf未知数 Kp1*Qp
cols = Kp1*Qp
A = np.zeros((Ns*Nf, cols), dtype=complex)
b = np.zeros(Ns*Nf, dtype=complex)
r = 0
for n,Hn in enumerate(H_samples):
phiμ = Φμ[n] # (Qp,)
# Φf (Nf,Kp1) 外积 phiμ -> (Nf, Kp1*Qp)
blk = np.einsum('fk,q->fkq', Φf, phiμ).reshape(Nf, cols)
A[r:r+Nf,:] = blk
b[r:r+Nf] = Hn
r += Nf
x, *_ = np.linalg.lstsq(A, b, rcond=None)
self.C = x.reshape(Kp1, Qp)
# 分母保持初始 (ĉ_0=1)
def _build_iter_system(self, H_samples, Φf, Φμ, D_prev):
Ns = len(H_samples); Nf,Kp1 = Φf.shape; Qp=Φμ.shape[1]
# 未知C (Kp1*Qp) + Ct (Kp1*Qp) 但固定 Ct[0,0]=1可去掉该变量
mask_fix = np.zeros((Kp1,Qp), dtype=bool)
mask_fix[0,0]=True
idx_map={}
# ---------- 7. t=1 (首轮 SK) 解 C, Ct ----------
def fit_t1_SK(H_list: List[np.ndarray], Phi_f: np.ndarray, Qμ: np.ndarray,
C_prev: np.ndarray, max_iter: int =1, tol: float =1e-3):
"""
只做一轮 (或少量) SK初始 D=1。
返回: C_new, Ct_new
"""
Ns = len(H_list); Nf,P = Phi_f.shape; V = .shape[1]
# 初始化分母系数 Ct: Ct[0,0]=1
Ct = np.zeros((P,V), dtype=complex)
Ct[0,0]=1.0
C = C_prev.copy()
for it in range(max_iter):
# 构建线性系统: (N - D*H)=0
# 未知顺序: C(:), Ct(:) 去掉固定 Ct[0,0]
mask_fix = np.zeros((P,V), dtype=bool); mask_fix[0,0]=True
col_map={}
col=0
for i in range(Kp1):
for j in range(Qp):
idx_map[('C',i,j)] = col; col+=1
for i in range(Kp1):
for j in range(Qp):
for i in range(P):
for j in range(V):
col_map[('C',i,j)]=col; col+=1
for i in range(P):
for j in range(V):
if mask_fix[i,j]: continue
idx_map[('Ct',i,j)] = col; col+=1
cols = col
rows = Ns*Nf
A = np.zeros((rows, cols), dtype=complex)
b = np.zeros(rows, dtype=complex)
col_map[('Ct',i,j)]=col; col+=1
A = np.zeros((Ns*Nf, col), dtype=complex)
b = np.zeros(Ns*Nf, dtype=complex)
r=0
for n,Hn in enumerate(H_samples):
phiμ = Φμ[n]
Dp = D_prev[:,n] # (Nf,)
invDp = 1.0/(Dp + 1e-12)
# 分子块: (Φf φμ^T) / D_prev
Num_blk = np.einsum('fk,q->fkq', Φf, phiμ).reshape(Nf, Kp1*Qp)
Num_blk = (Num_blk.T * invDp).T
# 评价当前 D_prev= Σ Ct φ ψ
for n,Hn in enumerate(H_list):
# 分子块 (Φ_f ⊗ ψ_n)
blk = np.einsum('fp,v->fpv', Phi_f, [n]).reshape(Nf, P*V)
# 填 C 部分
for i in range(Kp1):
for j in range(Qp):
A[r:r+Nf, idx_map[('C',i,j)]] = Num_blk[:, i*Qp + j]
# 分母块: -(H * Φf φμ^T)/D_prev
Den_blk = (Num_blk.T * Hn).T * (-1.0)
for i in range(Kp1):
for j in range(Qp):
for i in range(P):
for j in range(V):
A[r:r+Nf, col_map[('C',i,j)]] = blk[:, i*V + j]
# 分母块: - H * blk
for i in range(P):
for j in range(V):
if mask_fix[i,j]: continue
A[r:r+Nf, idx_map[('Ct',i,j)]] = Den_blk[:, i*Qp + j]
# 右端 0
r += Nf
# 约束行 (式5)(8) 可选Re( Σ_k D^{(t)}/D^{(t-1)} ) = K+1
if self.cfg.add_constraint:
row_c = np.zeros(cols, dtype=complex)
# D^{(t)} ≈ Σ Ct_k φ_k(s) ; 用代表样本 n=0, 对所有频率平均
n0=0
phiμ0=Φμ[n0]
mean_phiμ = phiμ0 # 可换平均
# φ_k(s) 平均
mean_phif = Φf.mean(0) # (Kp1,)
for i in range(Kp1):
for j in range(Qp):
if mask_fix[i,j]: continue
row_c[idx_map[('Ct',i,j)]] = mean_phif[i]*mean_phiμ[j]
rhs = (Kp1) # K+1
A = np.vstack([A, np.real(row_c) if self.cfg.real_split else row_c])
b = np.concatenate([b, [rhs]])
# 拆实虚
if self.cfg.real_split:
A_real = np.vstack([np.real(A), np.imag(A)])
b_real = np.concatenate([np.real(b), np.imag(b)])
else:
A_real, b_real = A, b
return A_real, b_real
def _unpack_iter_solution(self, x, Kp1, Qp):
# 重新填回 C, Ct (保持 Ct[0,0]=1)
# 构建与 _build_iter_system 相同的 idx_map
mask_fix = np.zeros((Kp1,Qp), dtype=bool); mask_fix[0,0]=True
idx_C = Kp1*Qp
# 注意:我们当时 C 索引从 0..(Kp1*Qp-1)
self.C = x[:idx_C].reshape(Kp1, Qp)
Ct_new = self.Ct.copy()
pos = idx_C
for i in range(Kp1):
for j in range(Qp):
A[r:r+Nf, col_map[('Ct',i,j)]] = - Hn * blk[:, i*V + j]
r+=Nf
# 求解
x, *_ = np.linalg.lstsq(A, b, rcond=None)
# 拆回
idx=0
for i in range(P):
for j in range(V):
C[i,j]=x[idx]; idx+=1
Ct_new = Ct.copy()
for i in range(P):
for j in range(V):
if mask_fix[i,j]: continue
Ct_new[i,j]=x[pos]; pos+=1
self.Ct = Ct_new
Ct_new[i,j]=x[idx]; idx+=1
# 收敛性简单检查:分母变化
diff = np.max(np.abs(Ct_new - Ct))
Ct = Ct_new
if diff < tol:
break
return C, Ct
def _eval_C_mu(self, phiμ):
# 返回 c_k(μ) (Kp1,)
return self.C @ phiμ
def _eval_Ct_mu(self, phiμ):
return self.Ct @ phiμ
def _eval_D(self, Φf, Φμ):
# D(f, sample)= Σ_k ĉ_k(μ_n) φ_k(f)
Kp1 = Φf.shape[1]
Ns = Φμ.shape[0]
D = np.zeros((Φf.shape[0], Ns), dtype=complex)
for n in range(Ns):
ct_mu = self._eval_Ct_mu(Φμ[n])
D[:,n] = Φf @ ct_mu
return D
# ---------- 8. 假极点检测 (在 raw 基上) ----------
def detect_spurious_poles(C_ml: np.ndarray, Ct_ml: np.ndarray, T: np.ndarray,
: np.ndarray,
tau_cancel=1e-2, tau_small=1e-3, eps=1e-14):
"""
C_ml, Ct_ml: (P,V) (Muntz-Laguerre 基)
T: Φ_ml T = G_raw
Qμ: (Ns,V)
"""
# raw 系数: c_raw = T^{-1} c_ml
Tinv = np.linalg.inv(T)
C_raw = Tinv @ C_ml
Ct_raw = Tinv @ Ct_ml
P,V = C_ml.shape
Ns = .shape[0]
r_vals = C_raw @ .T
q_vals = Ct_raw @ .T
metrics={}
scales=[]
for k in range(1,P): # 跳过常数列
rv = r_vals[k]; qv = q_vals[k]
diff = np.max(np.abs(rv - qv))
scale = np.max([np.max(np.abs(rv)), np.max(np.abs(qv)), eps])
eta = diff / scale
metrics[k] = {"diff": diff, "scale": scale, "eta": eta}
scales.append(scale)
S_max = max(scales) if scales else 1.0
cancel_spurious = [k for k in range(1,P)
if metrics[k]["eta"] < tau_cancel and metrics[k]["scale"] > tau_small * S_max]
small_spurious = [k for k in range(1,P)
if metrics[k]["scale"] <= tau_small * S_max]
return {
"cancel_spurious": cancel_spurious,
"small_spurious": small_spurious,
"metrics": metrics,
"S_max": S_max,
"C_raw": C_raw,
"Ct_raw": Ct_raw
}
def evaluate(self, freqs: np.ndarray, g: np.ndarray):
assert np.allclose(freqs, self.fb.freqs), "频率需在训练网格上 (示例简化)"
Φf = self.fb.Q # (Nf,K+1)
phiμ = self.pb.eval(g) # (Qp,)
num = Φf @ (self.C @ phiμ)
den = Φf @ (self.Ct @ phiμ)
return num / (den + 1e-15)
# ---------- 9. 统一入口 ----------
@dataclass
class OPVFResult:
C_ml: np.ndarray
Ct_ml: np.ndarray
C_raw: np.ndarray
Ct_raw: np.ndarray
T: np.ndarray
: np.ndarray
: np.ndarray
exps: List[Tuple[int]]
poles: List[complex]
spurious: Dict
def opvf_from_H(H_list: List[np.ndarray],
freqs_hz: np.ndarray,
param_matrix: np.ndarray,
total_degree: int,
poles: List[complex],
do_t1: bool = True) -> OPVFResult:
"""
H_list: 长度 Ns; 每项 shape (Nf,)
freqs_hz: (Nf,)
param_matrix: (Ns,d) 归一化前参数值 (内部不自动标准化以保持可控)
poles: 初始稳定极点列表
"""
# 频率基
Phi_ml = muntz_laguerre_basis(freqs_hz, poles) # (Nf,P)
G_raw = raw_partial_fraction_basis(freqs_hz, poles) # (Nf,P)
# 简单权 (ω 权): w = Δf (因 (1/2π)∫ φφ* dω => ∑ Δf)
w = _trap_weights(freqs_hz)
T = compute_transform_matrix(Phi_ml, G_raw, w)
# 参数基
, , exps = generate_param_basis(param_matrix, total_degree)
# t=0
C_ml = fit_t0(H_list, Phi_ml, )
if do_t1:
C_ml, Ct_ml = fit_t1_SK(H_list, Phi_ml, , C_ml, max_iter=1)
else:
# D=1
P,V = C_ml.shape
Ct_ml = np.zeros((P,V), dtype=complex)
Ct_ml[0,0]=1.0
# 假极点检测
spurious = detect_spurious_poles(C_ml, Ct_ml, T, )
return OPVFResult(
C_ml=C_ml,
Ct_ml=Ct_ml,
C_raw=spurious["C_raw"],
Ct_raw=spurious["Ct_raw"],
T=T,
=,
=,
exps=exps,
poles=poles,
spurious=spurious
)
# ---------- 辅助: 梯形权 ----------
def _trap_weights(f: np.ndarray):
if len(f)==1: return np.ones(1)
df = np.diff(f)
w = np.zeros_like(f)
w[0]=0.5*df[0]; w[-1]=0.5*df[-1]
if len(f)>2:
w[1:-1]=0.5*(df[:-1]+df[1:])
return w
# ---------- 简单测试占位 ----------
if __name__ == "__main__":
# 虚构数据: 2 个参数样本 (Ns=2), 频率 200 点
freqs = np.linspace(1e8, 5e9, 200)
# 真正模型 (示例): H = Σ_k R_k/(s - p_k)
true_poles = [-0.5e3 + 1.2e9j, -0.5e3 - 1.2e9j]
s = 1j*2*np.pi*freqs
def synth(Rs):
return Rs[0]/(s - true_poles[0]) + Rs[1]/(s - true_poles[1])
H_list = [synth([0.8+0.1j, 1.2-0.2j]), synth([0.9+0.05j, 1.1+0.1j])]
params = np.array([[0.0],[1.0]]) # 1 维参数
# 给一个冗余极点集合 (含真实 + 额外)
start_poles = generate_starting_poles(n_pairs=10, beta_min=5e8, beta_max=2.0e9, alpha_scale=0.000001)
res = opvf_from_H(H_list, freqs, params, total_degree=1, poles=start_poles)
print("C_ml shape:", res.C_ml.shape)
print("假极点(cancel):", res.spurious["cancel_spurious"])
print("假极点(small):", res.spurious["small_spurious"])