import numpy as np from core.sk_iter import generate_starting_poles from scipy.linalg import block_diag import skrf as rf from skrf import VectorFitting from core.freqency import auto_select class BasicBasis: def __init__(self,H,freqs,poles,weights=None): self.least_squares_rms_error = None self.least_squares_condition = None self.eigenval_condition = None self.eigenval_rms_error = None self.H = H self.freqs = freqs self.s = self.freqs * 2j * np.pi self.P = len(poles) self.poles = poles self.Phi = self.generate_basis(self.s, self.poles) self.A = self.matrix_A(self.poles) self.B = self.vector_B(self.poles) self.D = 1.0 self.Cr,self.Cw = self.fit_denominator(self.H, d0=1.0, weights=weights) z = np.linalg.eigvals(self.A - self.B @ self.Cw) p_next = -z # enforce LHP and pair ordering p_next = np.where(np.real(p_next) < 0, p_next, -np.conj(p_next)) p_next = np.sort_complex(p_next) self.next_poles = p_next # z = np.where(np.real(z) < 0, z, -np.conj(z)) # enforce LHP # self.next_poles = np.sort_complex(z) self.eigenval_condition = np.linalg.cond(self.A - self.B @ self.Cw) self.eigenval_rms_error = np.sqrt(np.mean(np.abs(np.real(z) - np.real(poles))**2 + np.abs(np.imag(z) - np.imag(poles))**2)) self.Dt = self.eval_Dt_state_space() self.delta = self.Dt / weights if weights is not None else self.Dt pass def eval_Dt_state_space(self): """Return D(s_k)=C(s_k I - A)^(-1)B + D for all k (complex 1D array).""" s = 1j * 2*np.pi * np.asarray(self.freqs, float).ravel() A = np.asarray(self.A, np.complex128); n = A.shape[0] B = np.asarray(self.B, np.complex128).reshape(n, 1) C = np.asarray(self.Cw, float).reshape(1, n) D = self.D I = np.eye(n, dtype=np.complex128) out = np.empty_like(s, dtype=np.complex128) for k, sk in enumerate(s): DS = D + (C @ np.linalg.inv(sk*I - A) @ B) out[k] = DS[0, 0] return out def generate_basis(self,s, poles): """Real basis of (15)-(16); returns Φ(s) and a layout for packing C.""" cols = [] i = 0 while i < len(poles): p = poles[i] if p.real > 0: raise ValueError("poles must be in the LHP") if i+1 < len(poles) and np.isclose(poles[i+1], np.conj(p)): pc = poles[i+1] phi1 = 1/(s - p) + 1/(s - pc) # eq (15)generate_basis phi2 = 1j*(1/(s - p) - 1/(s - pc)) # eq (16) (fixed sign) cols += [phi1, phi2] i += 2 else: cols.append(1/(s - p)) i += 1 Phi = np.column_stack(cols).astype(np.complex128) return Phi def matrix_A(self, poles): def A_block(p): if abs(p.imag) < 1e-14: return np.array([[p.real]], float) # A_p = [ p ] return np.array([[p.real, p.imag], # A_p = [[Re p, Im p], [-p.imag, p.real]], float) # [-Im p, Re p]] A = None; i = 0 while i < len(poles): p = poles[i] Ab = A_block(p) if i+1 < len(poles) and np.isclose(poles[i+1], np.conj(p)): i += 2 else: i += 1 A = Ab if A is None else block_diag(A, Ab) return A def vector_B(self, poles): def B_block(p): return np.array([[1.0]], float) if abs(p.imag)<1e-14 else np.array([[2.0],[0.0]], float) B = None; i = 0 while i < len(poles): p = poles[i] Bb = B_block(p) if i+1 < len(poles) and np.isclose(poles[i+1], np.conj(p)): i += 2 else: i += 1 B = Bb if B is None else np.vstack([B, Bb]) return B def fit_denominator(self, H, d0=1.0, weights=None): """ Solve formula (70) on the real basis Φ to obtain: - d (real) → packs into C for this state's block structure - gamma (complex) Optional 'weights' (K,) apply row scaling: SK weighting if 1/|D_prev|. """ if weights is None: weights = np.diag(np.ones(len(H), np.complex128)) else: weights = np.diag([1/res for res in weights]) s = self.s H = np.asarray(H, np.complex128).reshape(-1,1) Phi = self.Phi psi = weights @ Phi psi = Phi HPhi = H * Phi A_re = np.hstack([np.real(-psi), np.real(-HPhi)]) A_im = np.hstack([np.imag(-psi), np.imag(-HPhi)]) b_re = np.real(d0 * H) b_im = np.imag(d0 * H) A = np.vstack([A_re, A_im]).astype(float) # rown = np.linalg.norm(A, axis=1) # rown = np.sqrt(rown) # A = rown[:,None] * A b = np.concatenate([b_re, b_im]).astype(float) x = np.linalg.inv(A.T @ A) @ A.T @ b self.least_squares_rms_error = np.sqrt(np.mean((A @ x - b)**2)) self.least_squares_condition = np.linalg.cond(A) Cn,Cd = self.vector_C(x) return Cn,Cd def vector_C(self,x): Cn = np.asarray([x[:len(x)//2]], float).reshape(1,-1) Cd = np.asarray([x[len(x)//2:]], float).reshape(1,-1) return Cn, Cd def evaluate(self,freqs,poles,Cn,Cd,d0=1.0): s = 1j * 2*np.pi * np.asarray(freqs, float).ravel() phi = self.generate_basis(s, poles) num = phi @ Cn.T den = d0 + phi @ Cd.T H = num / den return H.ravel() if __name__ == "__main__": network = rf.Network("/tmp/paramer/simulation/3000/3000.s2p") K = 10 H11,freqs = auto_select([network.y[i][0][0] for i in range(2,len(network.y))],network.f[2:],max_points=20) poles = generate_starting_poles(2,beta_min=freqs[0]/1.1,beta_max=freqs[-1]*1.1) Dt_1 = np.ones((len(freqs),1),np.complex128) # Levi step (no weighting): basis = BasicBasis(H11,freqs,poles=poles) Dt = basis.Dt poles = basis.next_poles print("Levi step (no weighting):") print("A:",basis.A) print("B:",basis.B) print("C:",basis.Cw) print("D:",basis.D) print("next_pozles:",basis.next_poles) print("Dt:",Dt, "norm:",np.linalg.norm(Dt)) # SK weighting (optional, after first pass): least_squares_condition = [] least_squares_rms_error = [] eigenval_condition = [] eigenval_rms_error = [] for i in range(K): basis = BasicBasis(H11,freqs,poles=poles,weights=Dt) Dt_1 = Dt Dt = basis.Dt poles = basis.next_poles print(f"SK Iteration {i+1}/{K}") print("A:",basis.A) print("B:",basis.B) print("C:",basis.Cw) print("D:",basis.D) print("z:",basis.next_poles) print("Dt:",Dt) print("Dt/Dt-1",np.linalg.norm(Dt) / np.linalg.norm(Dt_1)) least_squares_condition.append(basis.least_squares_condition) least_squares_rms_error.append(basis.least_squares_rms_error) eigenval_condition.append(basis.eigenval_condition) eigenval_rms_error.append(basis.eigenval_rms_error) # H11_evaluated = basis.evaluate_pole_residue(network.f[1:],poles,basis.C[0]) H11_evaluated = basis.evaluate(network.f[2:], poles, basis.Cr[0],basis.Cw[0], d0=1.0) import matplotlib.pyplot as plt fig, axes = plt.subplots(3, 2, figsize=(15, 16), sharex=False) ax0 = axes[0][0] ax0.plot(network.f[2:], np.abs([network.y[i][0][0] for i in range(2,len(network.y))]), 'o', ms=4, color='red', label='Samples') ax0.plot(network.f[2:], np.abs(H11_evaluated), '-', lw=2, color='k', label='Fit') ax0.plot(freqs, np.abs(H11), 'x', ms=4, color='blue', label='Input Samples') ax0.set_title("Response i=0, j=0") ax0.set_ylabel("Magnitude") ax0.legend(loc="best") ax1 = axes[1][0] ax1.plot(least_squares_condition, label='Least Squares Condition') ax1.set_title("least_squares_condition") ax1.set_ylabel("Magnitude") ax1.legend(loc="best") ax2 = axes[1][1] ax2.plot(least_squares_rms_error, label='Least Squares RMS Error') ax2.set_title("least_squares_rms_error") ax2.set_ylabel("Magnitude") ax2.legend(loc="best") ax3 = axes[2][0] ax3.plot(eigenval_condition, label='Eigenvalue Condition') ax3.set_title("eigenval_condition") ax3.set_ylabel("Magnitude") ax3.legend(loc="best") ax4 = axes[2][1] ax4.plot(eigenval_rms_error, label='Eigenvalue RMS Error') ax4.set_title("eigenval_rms_error") ax4.set_ylabel("Magnitude") ax4.legend(loc="best") fig.tight_layout() plt.savefig(f"basic_basis.png")