242 lines
8.7 KiB
Python
242 lines
8.7 KiB
Python
import numpy as np
|
|
from core.utils import generate_starting_poles
|
|
from scipy.linalg import block_diag
|
|
import skrf as rf
|
|
from skrf import VectorFitting
|
|
from core.sample 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")
|
|
|
|
|
|
|
|
|