diff --git a/core/orthonormal_basis.py b/core/orthonormal_basis.py new file mode 100644 index 0000000..096dea2 --- /dev/null +++ b/core/orthonormal_basis.py @@ -0,0 +1,174 @@ +import numpy as np + +# ------------------------------------------------------------ +# 按论文公式 (9)(10)(11) 生成 Muntz–Laguerre 正交有理基 (解析形式): +# +# 给定稳定极点集合 {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) * Π_{i0 的 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= 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= 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}") \ No newline at end of file diff --git a/core/sk_iter.py b/core/sk_iter.py index 0cda0f1..f230398 100644 --- a/core/sk_iter.py +++ b/core/sk_iter.py @@ -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. Muntz–Laguerre 频率基 (与你现有 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, Qμ[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 = Qμ.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, Qμ[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, + Qμ: 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 = Qμ.shape[0] + r_vals = C_raw @ Qμ.T + q_vals = Ct_raw @ Qμ.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) \ No newline at end of file +# ---------- 9. 统一入口 ---------- +@dataclass +class OPVFResult: + C_ml: np.ndarray + Ct_ml: np.ndarray + C_raw: np.ndarray + Ct_raw: np.ndarray + T: np.ndarray + Qμ: np.ndarray + Rμ: 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) + + # 参数基 + Qμ, Rμ, exps = generate_param_basis(param_matrix, total_degree) + + # t=0 + C_ml = fit_t0(H_list, Phi_ml, Qμ) + + if do_t1: + C_ml, Ct_ml = fit_t1_SK(H_list, Phi_ml, Qμ, 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, Qμ) + + return OPVFResult( + C_ml=C_ml, + Ct_ml=Ct_ml, + C_raw=spurious["C_raw"], + Ct_raw=spurious["Ct_raw"], + T=T, + Qμ=Qμ, + Rμ=Rμ, + 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"]) \ No newline at end of file diff --git a/docs/内积与基函数正交归一化的定义.md b/docs/内积与基函数正交归一化的定义.md index afd179f..9de7e0c 100644 --- a/docs/内积与基函数正交归一化的定义.md +++ b/docs/内积与基函数正交归一化的定义.md @@ -1,3 +1,5 @@ +## 留数定理 + ### 1. 正交归一化条件 首先给出了一组基函数 \(\phi_m(s)\),要求它们满足正交归一化条件: diff --git a/docs/有理函数正交化.md b/docs/有理函数正交化.md new file mode 100644 index 0000000..49da11c --- /dev/null +++ b/docs/有理函数正交化.md @@ -0,0 +1,13 @@ +$$ +\begin{align} +&N(s) = \sum^N_{i=0}n_is^i=\xi_1\prod^N_{i=0}(s-p_{ni})\\ +&D(s) = \sum^M_{i=0}d_is^i=\xi_2\prod^M_{i=0}(s-p_{mi})\\ +&H(s) = \xi\frac{\prod^N_{i=0}(s-p_{ni})}{\prod^M_{i=0}(s-p_{mi})}\\ +&let \space \phi_x=\frac{1}{s-p_x}\\ +&H(s) = \frac{\sum_{i=0}^Nc_{ni}\phi_i(s)}{\sum_{j=0}^Mc_{mj}\phi_j(s)}\\ +&当M=N时,通分之后得到\\ +&H(s) = \frac{\frac{\sum_{i=0}^Nc_i\prod_{a=0,a\not=i}(s-p_a)}{\prod^N_{i=0}(s-p_i)}}{\frac{\sum_{j=0}^Nc_j\prod_{b=0,b\not=j}(s-p_b)}{\prod^N_{j=0}(s-p_j)}}\\ +&此时由于H(s)分母的分子连乘部分存在缺项,所以p_b为假极点 +\end{align} +$$ + diff --git a/env b/env new file mode 100644 index 0000000..27df2ed --- /dev/null +++ b/env @@ -0,0 +1,2 @@ +export PYTHONPATH=$(pwd)/core:$PYTHONPATH +export PYTHONPATH=$(pwd)/models:$PYTHONPATH \ No newline at end of file diff --git a/img.png b/img.png new file mode 100644 index 0000000..fd59237 Binary files /dev/null and b/img.png differ diff --git a/main.py b/main.py index 609d240..b9dc3ee 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,117 @@ -from models.capa import Capa -W = [] -L = [] -i = 15.52 - -while i <= 100: - W.append(i) - L.append(i) - i = int(i*1.05*100 +0.5) / 100.0 - -capa = Capa() -capa.sweep(W,L) -print(capa.network(result_dir=capa.results[0].result_dir,id=capa.results[0].id)) +from core.orthonormal_basis import generate_laguerre_basis +from core.sk_iter import generate_starting_poles +import numpy as np +import skrf as rf +import matplotlib.pyplot as plt +import sympy as sp +from scipy.linalg import null_space + + +network = rf.Network("/tmp/paramer/simulation/4000/4000.s2p") +freqs = network.f +s = freqs * 2j * np.pi +vf = rf.VectorFitting(network) +vf.vector_fit(n_poles_real=2, n_poles_cmplx=1,parameter_type="y") + +poles = vf.poles +residues = vf.residues +y = vf.network.y + +fig, ax = plt.subplots(2, 2) +fig.set_size_inches(12, 8) +vf.plot("mag",0,0,freqs,ax=ax[0][0],parameter="y") +rms_error11 = vf.get_rms_error(0,0,"y") +print("rms_error11",rms_error11) +vf.plot("mag",1,0,freqs,ax=ax[1][0],parameter="y") +rms_error21 = vf.get_rms_error(1,0,"y") +print("rms_error21",rms_error21) +vf.plot("mag",0,1,freqs,ax=ax[0][1],parameter="y") +rms_error12 = vf.get_rms_error(0,1,"y") +print("rms_error12",rms_error12) +vf.plot("mag",1,1,freqs,ax=ax[1][1],parameter="y") +rms_error22 = vf.get_rms_error(1,1,"y") +print("rms_error22",rms_error22) +fig.tight_layout() +plt.show() +plt.savefig(f"img.png") + +diag_values = [y[i][0][0] for i in range(len(y))] +H = np.diag(diag_values) +P = 4 +start_poles = generate_starting_poles(P,beta_min=1e8,beta_max=freqs[-1]) +basis = generate_laguerre_basis(start_poles,s).T +print("start_poles",start_poles) +print("basis",basis) + +# first step iteration +# A*x = b + +A11 = np.real(basis) +print("A11 shape:",A11.shape) +A12 = np.real(- H @ basis[:,1:]) +print("A12 shape:",A12.shape) +# print("A11",A11) +A21 = np.imag(basis) +print("A21 shape:",A21.shape) +A22 = np.imag(- H @ basis[:,1:]) +print("A22 shape:",A22.shape) +A1 = np.hstack([A11,A12]) +A2 = np.hstack([A21,A22]) +A = np.vstack([A1,A2]) +print ("A shape:",A.shape) + +b1 = np.real(H @ basis[:,0]) +b2 = np.imag(H @ basis[:,0]) +b = np.hstack([b1,b2]) +Q, R = np.linalg.qr(A, mode='reduced') +print("Q :",Q) +print("R :",R) +print("b shape:",b.shape) +# x = np.linalg.solve(R, Q.T @ b) +x_star, residuals, rank, s = np.linalg.lstsq(A, b, rcond=None) + +# print("x_qr",x_star) +print("residuals",residuals) +# print("rank",rank) +# print("s",s) + +x_ne = np.linalg.inv(A.T @ A) @ A.T @ b +print("x_ne",x_ne) + +# sk iteration +target_vec = np.array([1+0j] + list(x_ne[len(x_ne)//2+1:])) +D_t = basis @ target_vec +print("D_t",D_t) +K = 25 + +for i in range(K): + print(f"Iteration {i+1}/{K}") + A11 = np.real(basis / D_t[:, np.newaxis]) + A12 = np.real(- H @ basis[:,1:] / D_t[:, np.newaxis]) + A21 = np.imag(basis / D_t[:, np.newaxis]) + A22 = np.imag(- H @ basis[:,1:] / D_t[:, np.newaxis]) + A1 = np.hstack([A11,A12]) + A2 = np.hstack([A21,A22]) + A = np.vstack([A1,A2]) + b1 = np.real(H @ basis[:,0]) + b2 = np.imag(H @ basis[:,0]) + b = np.hstack([b1,b2]) + x_star, residuals, rank, s = np.linalg.lstsq(A, b, rcond=None) + # print("x_lstsq",x_star) + # print("residuals",residuals) + # print("rank",rank) + # print("s",s) + x_ne = np.linalg.inv(A.T @ A) @ A.T @ b + # print("x_ne",x_ne) + target_vec = np.array([1+0j] + list(x_ne[len(x_ne)//2+1:])) + D_t_pre = D_t + D_t = basis @ target_vec + print("D_t/D_t_pre",np.linalg.norm(D_t)/np.linalg.norm(D_t_pre)) + # print("D_t",D_t) + n_target_vec = np.array(list(x_ne[:len(x_ne)//2+1])) + N_t = basis @ n_target_vec + # print("H = N_t / D_t",np.linalg.norm(H - N_t / D_t)/np.linalg.norm(H)) + + diff --git a/test/lazy_evaulation.py b/test/lazy_evaulation.py deleted file mode 100644 index c5ab46f..0000000 --- a/test/lazy_evaulation.py +++ /dev/null @@ -1,202 +0,0 @@ -import numpy as np - -# ------------------------------------------------------------ -# 按论文公式 (9)(10)(11) 生成 Muntz–Laguerre 正交有理基 (解析形式): -# -# 给定稳定极点集合 {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) * Π_{i0 的 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= 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, stable_poles: list | np.ndarray): - """ - 生成完整基函数列表: [φ_0=1, φ_1, φ_2, ...] - """ - basis = [np.ones_like(s, dtype=complex)] - for block in MuntzLaguerreIterator(s, stable_poles): - basis.extend(block) - return basis - -# ---------- 可选:离散再正交 (加权 QR) ---------- -# def trapezoid_weights(freqs: np.ndarray): -# if len(freqs) == 1: -# return np.ones(1) -# df = np.diff(freqs) -# w = np.zeros_like(freqs, dtype=float) -# w[0] = 0.5 * df[0] -# w[-1] = 0.5 * df[-1] -# if len(freqs) > 2: -# w[1:-1] = 0.5 * (df[:-1] + df[1:]) -# return w - -def weighted_qr_from_basis(basis_cols: list[np.ndarray], weights: np.ndarray | None = None): - A = np.column_stack(basis_cols) # (Nf, M) - if weights is None: - sw = np.ones(A.shape[0]) - else: - sw = np.sqrt(weights.real) - Aw = sw[:, None] * A - Qw, R = np.linalg.qr(Aw) - Phi = Qw / (sw[:, None] + 1e-30) - return Phi, R # Raw = Phi R - -def check_discrete_orthogonality(Phi: np.ndarray, w: np.ndarray): - G = Phi.conj().T @ (w[:, None] * Phi) - off = np.max(np.abs(G - np.eye(G.shape[0]))) - return G, off - -def verify_orthonormal(Phi: np.ndarray, w: np.ndarray, atol=1e-10, rtol=1e-8): - """ - 返回: - G : Gram 矩阵 (Φ^H W Φ) - max_off : 最大非对角幅值 - diag_err : max |diag(G)-1| - passed : 是否满足阈值 - """ - G = Phi.conj().T @ (w[:, None] * Phi) - I = np.eye(G.shape[0]) - diag_err = np.max(np.abs(np.diag(G) - 1.0)) - max_off = np.max(np.abs(G - I + np.diag(np.diag(G) - 1.0))) - passed = (diag_err <= atol) and (max_off <= rtol) - return G, max_off, diag_err, passed - -def omega_weights(freqs_hz: np.ndarray): - """ - 基于 ω=2πf 的梯形法得到 w_ω = Δω/(2π),使得 - (1/2π) ∫_{-∞}^{∞} → Σ w_ω,k - """ - f = freqs_hz - if len(f) == 1: - return np.ones(1) - df = np.diff(f) - w_f = np.zeros_like(f) - w_f[0] = 0.5 * df[0] - w_f[-1] = 0.5 * df[-1] - if len(f) > 2: - w_f[1:-1] = 0.5 * (df[:-1] + df[1:]) - # dω = 2π df, (1/2π) * dω = df ⇒ 直接 w_f 就是 w_ω - return w_f # 已等价于 Δω/(2π) - -# ------------------ 示例 ------------------ -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("解析基函数数量 =", len(basis)) - print("前5个基函数示例 (每个前10个频点):") - for i in range(5): - print(f"φ_{i}:", basis[i][:10]) - - w = omega_weights(freqs) - Phi_num, R = weighted_qr_from_basis(basis, w) - Gram, off = check_discrete_orthogonality(Phi_num, w) - print("离散 Gram 最大非对角元素 =", off) - print("R 形状:", R.shape) - # 验证 Raw ≈ Phi R - raw = np.column_stack(basis) - err = np.max(np.abs(raw - Phi_num @ R)) - print("重构误差 ||Raw - Phi R||_∞ =", err) - - # 验证正交性 - print("离散 Gram 矩阵 (前5x5):") - print(Gram[:5, :5]) - - Gcheck, max_off, diag_err, ok = verify_orthonormal(Phi_num, w) - print(f"Diag 误差={diag_err:.3e}, Max off={max_off:.3e}, Orthonormal={ok}") - # 额外: 验证 R - # raw = Φ R => R ≈ Φ^H W raw (因为 Φ^H W Φ = I) - R_alt = Phi_num.conj().T @ (w[:,None] * raw) - print("R 差异 ||R - R_alt||_max =", np.max(np.abs(R - R_alt))) diff --git a/test/test_numpy.py b/test/test_numpy.py new file mode 100644 index 0000000..5e03142 --- /dev/null +++ b/test/test_numpy.py @@ -0,0 +1,9 @@ +import numpy as np + +# 创建一个复数矩阵 +A = np.array([[1+2j, 2-1j], [3+4j, 4+0j]]) + +Q, R = np.linalg.qr(A) + +print("Q =\n", Q) +print("R =\n", R) \ No newline at end of file diff --git a/test/test_orthonormal_basis.py b/test/test_orthonormal_basis.py new file mode 100644 index 0000000..c5116b4 --- /dev/null +++ b/test/test_orthonormal_basis.py @@ -0,0 +1,102 @@ +import numpy as np +from core.orthonormal_basis import generate_muntz_laguerre_basis + +# ---------- 可选:离散再正交 (加权 QR) ---------- +# def trapezoid_weights(freqs: np.ndarray): +# if len(freqs) == 1: +# return np.ones(1) +# df = np.diff(freqs) +# w = np.zeros_like(freqs, dtype=float) +# w[0] = 0.5 * df[0] +# w[-1] = 0.5 * df[-1] +# if len(freqs) > 2: +# w[1:-1] = 0.5 * (df[:-1] + df[1:]) +# return w + +def weighted_qr_from_basis(basis_cols: list[np.ndarray], weights: np.ndarray | None = None): + A = np.column_stack(basis_cols) # (Nf, M) + if weights is None: + sw = np.ones(A.shape[0]) + else: + sw = np.sqrt(weights.real) + Aw = sw[:, None] * A + Qw, R = np.linalg.qr(Aw) + Phi = Qw / (sw[:, None] + 1e-30) + return Phi, R # Raw = Phi R + +def check_discrete_orthogonality(Phi: np.ndarray, w: np.ndarray): + G = Phi.conj().T @ (w[:, None] * Phi) + off = np.max(np.abs(G - np.eye(G.shape[0]))) + return G, off + +def verify_orthonormal(Phi: np.ndarray, w: np.ndarray, atol=1e-10, rtol=1e-8): + """ + 返回: + G : Gram 矩阵 (Φ^H W Φ) + max_off : 最大非对角幅值 + diag_err : max |diag(G)-1| + passed : 是否满足阈值 + """ + G = Phi.conj().T @ (w[:, None] * Phi) + I = np.eye(G.shape[0]) + diag_err = np.max(np.abs(np.diag(G) - 1.0)) + max_off = np.max(np.abs(G - I + np.diag(np.diag(G) - 1.0))) + passed = (diag_err <= atol) and (max_off <= rtol) + return G, max_off, diag_err, passed + +def omega_weights(freqs_hz: np.ndarray): + """ + 基于 ω=2πf 的梯形法得到 w_ω = Δω/(2π),使得 + (1/2π) ∫_{-∞}^{∞} → Σ w_ω,k + """ + f = freqs_hz + if len(f) == 1: + return np.ones(1) + df = np.diff(f) + w_f = np.zeros_like(f) + w_f[0] = 0.5 * df[0] + w_f[-1] = 0.5 * df[-1] + if len(f) > 2: + w_f[1:-1] = 0.5 * (df[:-1] + df[1:]) + # dω = 2π df, (1/2π) * dω = df ⇒ 直接 w_f 就是 w_ω + return w_f # 已等价于 Δω/(2π) + +def evaluate(basis,freqs): + w = omega_weights(freqs) + Phi_num, R = weighted_qr_from_basis(basis, w) + Gram, off = check_discrete_orthogonality(Phi_num, w) + print("离散 Gram 最大非对角元素 =", off) + print("R 形状:", R.shape) + # 验证 Raw ≈ Phi R + raw = np.column_stack(basis) + err = np.max(np.abs(raw - Phi_num @ R)) + print("重构误差 ||Raw - Phi R||_∞ =", err) + + # 验证正交性 + print("离散 Gram 矩阵 (前5x5):") + print(Gram[:5, :5]) + + Gcheck, max_off, diag_err, ok = verify_orthonormal(Phi_num, w) + print(f"Diag 误差={diag_err:.3e}, Max off={max_off:.3e}, Orthonormal={ok}") + # 额外: 验证 R + # raw = Φ R => R ≈ Φ^H W raw (因为 Φ^H W Φ = I) + R_alt = Phi_num.conj().T @ (w[:,None] * raw) + print("R 差异 ||R - R_alt||_max =", np.max(np.abs(R - R_alt))) + +# ------------------ 示例 ------------------ +if __name__ == "__main__": + # 示例稳定极点 (复对正虚部在前) + init_poles = [ + -1.0e3 + 2.5e9j, + -1.0e3 - 2.5e9j, + ] + freqs = np.linspace(1e8, 8e9, 40) + s = 1j * 2 * np.pi * freqs + + basis = generate_muntz_laguerre_basis(s, init_poles) + print("解析基函数数量 =", len(basis)) + print("基函数:") + for i in range(len(basis)): + print(f"φ_{i}:", basis[i][:]) + + evaluate(basis, freqs)