chore: 分离了sweep和vf部分,vf部分准备写为包
This commit is contained in:
@@ -1,626 +0,0 @@
|
||||
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_multple_ports
|
||||
import matplotlib.pyplot as plt
|
||||
import random as rnd
|
||||
|
||||
def cond_row_inf(A, use_pinv=True):
|
||||
"""行条件数 κ∞(A) = ||A||∞ * ||A^{-1}||∞;矩形阵用广义逆。"""
|
||||
A = np.asarray(A)
|
||||
Ainv = np.linalg.pinv(A) if (use_pinv or A.shape[0] != A.shape[1]) else np.linalg.inv(A)
|
||||
return np.linalg.norm(A, ord=np.inf) * np.linalg.norm(Ainv, ord=np.inf)
|
||||
|
||||
def cond_col_one(A, use_pinv=True):
|
||||
"""列条件数 κ1(A) = ||A||1 * ||A^{-1}||1;矩形阵用广义逆。"""
|
||||
A = np.asarray(A)
|
||||
Ainv = np.linalg.pinv(A) if (use_pinv or A.shape[0] != A.shape[1]) else np.linalg.inv(A)
|
||||
return np.linalg.norm(A, ord=1) * np.linalg.norm(Ainv, ord=1)
|
||||
|
||||
class MultiPortOrthonormalBasis:
|
||||
def __init__(self,H,freqs,poles,weights=None,passivity=True,dc_enforce=True,fit_constant=True,fit_proportional=False):
|
||||
self.least_squares_condition = None
|
||||
self.least_squares_row_condition = None
|
||||
self.least_squares_col_condition = None
|
||||
self.least_squares_rms_error = None
|
||||
self.eigenval_condition = None
|
||||
self.eigenval_row_condition = None
|
||||
self.eigenval_col_condition = None
|
||||
self.eigenval_rms_error = None
|
||||
self.Cr = None
|
||||
|
||||
self.dc_tol = 1e-18
|
||||
|
||||
self.dc_enforce = dc_enforce
|
||||
self.fit_constant = fit_constant
|
||||
self.fit_proportional = fit_proportional
|
||||
|
||||
self.freqs = freqs
|
||||
self.H = H
|
||||
self.ports = H.shape[1]
|
||||
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.C,self.w0,self.e = self.fit_denominator(self.H, weights=weights)
|
||||
self.D = self.w0
|
||||
|
||||
self.residuals = self.C / np.sqrt(2 * np.real(-np.array(self.poles)))
|
||||
|
||||
z = np.linalg.eigvals(self.A - self.B @ self.C)
|
||||
|
||||
if passivity:
|
||||
self.next_poles = self.passivity_enforce(z)
|
||||
else:
|
||||
self.next_poles = z
|
||||
|
||||
self.eigenval_condition,\
|
||||
self.eigenval_row_condition,\
|
||||
self.eigenval_col_condition,\
|
||||
self.eigenval_rms_error = self.eigen_metric()
|
||||
|
||||
self.Dt = self.eval_Dt_state_space()
|
||||
self.Dt_Dt_1 = np.linalg.norm(self.Dt) / np.linalg.norm(weights) if weights is not None else np.linalg.norm(self.Dt)
|
||||
pass
|
||||
|
||||
def eigen_metric(self):
|
||||
|
||||
"""Return condition number and RMS error of eigenvalues of A-BC."""
|
||||
z = np.linalg.eigvals(self.A - self.B @ self.C)
|
||||
cond = np.linalg.cond(self.A - self.B @ self.C)
|
||||
rms = np.sqrt(np.mean(np.abs(np.real(z) - np.real(self.poles))**2 + np.abs(np.imag(z) - np.imag(self.poles))**2))
|
||||
|
||||
row_cond = cond_row_inf(self.A - self.B @ self.C)
|
||||
col_cond = cond_col_one(self.A - self.B @ self.C)
|
||||
|
||||
return cond,row_cond,col_cond,rms
|
||||
|
||||
def least_squares_metric(self,A,b):
|
||||
"""Return condition number and RMS error of least-squares matrix A and rhs b."""
|
||||
cond = np.linalg.cond(A)
|
||||
rms = np.sqrt(np.mean((A @ np.linalg.pinv(A) @ b - b)**2))
|
||||
|
||||
row_cond = cond_row_inf(A)
|
||||
col_cond = cond_col_one(A)
|
||||
|
||||
return cond,row_cond,col_cond,rms
|
||||
|
||||
|
||||
def passivity_enforce(self,poles):
|
||||
"""enforce poles' real parts to be negative"""
|
||||
enforced_poles = []
|
||||
for pole in poles:
|
||||
if pole.real > 0:
|
||||
pole = -np.conj(pole)
|
||||
enforced_poles.append(pole)
|
||||
return enforced_poles
|
||||
|
||||
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, float); n = A.shape[0]
|
||||
B = np.asarray(self.B, float).reshape(n, 1)
|
||||
C = np.asarray(self.C, float).reshape(1, n)
|
||||
D = self.D
|
||||
I = np.eye(n, dtype=float)
|
||||
out = np.empty_like(s, dtype=float)
|
||||
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."""
|
||||
def all_pass(s,ap_list):
|
||||
res = 1.0 +0.0j
|
||||
for ap in ap_list:
|
||||
res *= (s - np.conj(ap)) / (s + ap)
|
||||
return res
|
||||
|
||||
cols = []
|
||||
ap_list = []
|
||||
i = 0
|
||||
while i < len(poles):
|
||||
ap = -poles[i]
|
||||
if ap.real < 0:
|
||||
raise ValueError("poles must be in the LHP")
|
||||
if i+1 < len(poles) and np.isclose(poles[i+1], np.conj(-ap)):
|
||||
ap1 = - poles[i+1]
|
||||
phi1 = np.sqrt(2 * ap.real) * all_pass(s, ap_list) * ((s - np.abs(ap))/((s + ap)*(s + ap1)))
|
||||
phi2 = np.sqrt(2 * ap.real) * all_pass(s, ap_list) * ((s + np.abs(ap))/((s + ap)*(s + ap1)))
|
||||
cols += [phi1, phi2]
|
||||
i += 2
|
||||
ap_list.append(ap)
|
||||
ap_list.append(ap1)
|
||||
else:
|
||||
basis = np.sqrt(2 * ap.real) * all_pass(s, ap_list) * (1/(s + ap))
|
||||
cols.append(basis)
|
||||
i += 1
|
||||
ap_list.append(ap)
|
||||
Phi = np.column_stack(cols).astype(np.complex128)
|
||||
return Phi
|
||||
|
||||
def matrix_A(self, poles):
|
||||
def A_col(p:np.complex128,index:int):
|
||||
ap = -p
|
||||
if abs(ap.imag) < 1e-14:
|
||||
col = []
|
||||
for i in range(index):
|
||||
col.append(0.0)
|
||||
col.append(-ap.real)
|
||||
for i in range(len(poles)-index-1):
|
||||
col.append(2*(-ap).real)
|
||||
return np.array([col], float)
|
||||
else:
|
||||
col1 = []
|
||||
col2 = []
|
||||
for i in range(index):
|
||||
col1.append(0.0)
|
||||
col2.append(0.0)
|
||||
col1.append(-ap.real); col2.append(-ap.real - np.abs(ap))
|
||||
col1.append(-ap.real + np.abs(ap)); col2.append(-ap.real)
|
||||
for i in range(len(poles)-index-2):
|
||||
col1.append(2*(-ap).real)
|
||||
col2.append(2*(-ap).real)
|
||||
return np.array([col1, col2], float)
|
||||
|
||||
i = 0
|
||||
cols = []
|
||||
while i < len(poles):
|
||||
p = poles[i]
|
||||
cols.extend(A_col(p,i))
|
||||
if i+1 < len(poles) and np.isclose(poles[i+1], np.conj(p)): i += 2
|
||||
else: i += 1
|
||||
A = np.column_stack(cols).astype(float)
|
||||
return A
|
||||
|
||||
def vector_B(self, poles):
|
||||
return np.ones((len(poles), 1), float)
|
||||
|
||||
def fit_denominator(self, H, weights=None, d0 = 1.0):
|
||||
"""
|
||||
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|.
|
||||
"""
|
||||
K, N = self.Phi.shape
|
||||
one = np.ones((K, 1), np.complex128)
|
||||
Phi = self.Phi
|
||||
dc_tol = 1e-18
|
||||
has_dc = self.dc_enforce and self.freqs[0] < dc_tol
|
||||
keep = np.ones(K, dtype=bool)
|
||||
|
||||
# SK weighting (applied only to the (73) rows we keep in LS)
|
||||
|
||||
if has_dc:
|
||||
# Enforce DC response exactly:
|
||||
k0 = int(np.argmin(np.abs(self.freqs)))
|
||||
keep[k0] = False
|
||||
|
||||
if self.fit_constant:
|
||||
Phi_w = np.hstack([one, Phi])
|
||||
index = 0
|
||||
M_kp = None
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
M0 = np.zeros((K,N*self.ports**2),dtype=complex)
|
||||
M0[:,index*N:(index+1)*N] = Phi
|
||||
M0 = np.hstack([M0, -(H[:,i,j].reshape(-1,1) * Phi_w)]).reshape((K, -1))[keep,:] # (K, 2N), complex
|
||||
index+=1
|
||||
M_kp = M0 if M_kp is None else np.vstack([M_kp, M0])
|
||||
assert M_kp is not None
|
||||
else:
|
||||
index = 0
|
||||
M_kp = None
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
M0 = np.zeros((K,N*self.ports**2),dtype=complex)
|
||||
M0[:,index*N:(index+1)*N] = Phi
|
||||
M0 = np.hstack([M0, -(H[:,i,j].reshape(-1,1) * Phi)]).reshape((K, -1))[keep,:] # (K, 2N), complex
|
||||
index+=1
|
||||
M_kp = M0 if M_kp is None else np.vstack([M_kp, M0])
|
||||
assert M_kp is not None
|
||||
|
||||
if weights is None:
|
||||
weights_kp = np.diag(np.ones(len(self.freqs[keep]) * self.ports**2, np.complex128))
|
||||
else:
|
||||
weights_kp0 = weights[keep]
|
||||
weights0 = []
|
||||
for i in range(self.ports **2 ):
|
||||
for res in weights_kp0:
|
||||
weights0.append(1/res)
|
||||
weights_kp = np.diag(np.array(weights0))
|
||||
|
||||
|
||||
if has_dc:
|
||||
M_w_kp = weights_kp @ M_kp
|
||||
A_re = np.real(M_w_kp)
|
||||
A_im = np.imag(M_w_kp)
|
||||
mask = np.ones(K, dtype=bool); mask[k0] = False
|
||||
# exact (unweighted) DC rows:
|
||||
# A_dc_re = np.real(M_kp).reshape(1, -1)
|
||||
# A_dc_im = np.imag(M_kp).reshape(1, -1)
|
||||
else:
|
||||
M_w_kp = weights_kp @ M_kp
|
||||
A_re = np.real(M_w_kp)
|
||||
A_im = np.imag(M_w_kp)
|
||||
# A_dc_re = A_dc_im = None
|
||||
|
||||
A_blocks = [A_re, A_im]
|
||||
|
||||
if self.fit_constant:
|
||||
Hk_sum = []
|
||||
for i in range(self.ports):
|
||||
Hk_sum.append([])
|
||||
for j in range(self.ports):
|
||||
Hk_kp0 = H[:,i,j][keep]
|
||||
Hk_sum[i].append(np.sum(np.abs(Hk_kp0)**2))
|
||||
# Hk_kp = Hk_kp0 if Hk_kp is None else np.hstack([Hk_kp, Hk_kp0])
|
||||
K_keep = int(np.count_nonzero(keep))
|
||||
A_w0 = []
|
||||
b_w0 = []
|
||||
# Hk_sum = np.sum(np.abs(Hk_kp)**2)
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
beta_ij = float(np.sqrt(Hk_sum[i][j]))
|
||||
mean_row = (beta_ij / K_keep) * np.sum(Phi_w[keep, :], axis=0)
|
||||
A_w0.append(np.concatenate([np.zeros(N*self.ports**2, float),
|
||||
np.real(mean_row).astype(float)]
|
||||
).reshape(1, -1))
|
||||
b_w0.append(np.array([beta_ij], float))
|
||||
b_w0 = np.asarray(b_w0).ravel()
|
||||
|
||||
A_blocks += A_w0
|
||||
m = A_re.shape[0] + A_im.shape[0]
|
||||
b = np.zeros(m, float)
|
||||
b = np.concatenate([b, b_w0])
|
||||
else:
|
||||
H_kp = None
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
H_0 = H[:,i,j][keep]
|
||||
H_kp = H_0 if H_kp is None else np.hstack([H_kp, H_0])
|
||||
assert H_kp is not None
|
||||
H_kp = weights_kp @ H_kp.reshape(-1,1)
|
||||
|
||||
b_re = np.real(d0 * H_kp)
|
||||
b_im = np.imag(d0 * H_kp)
|
||||
b = np.concatenate([b_re.ravel(), b_im.ravel()]).astype(float)
|
||||
|
||||
# ---- build final stacked-real system ----
|
||||
|
||||
|
||||
# if A_dc_re is not None:
|
||||
# A_blocks += [A_dc_re, A_dc_im]
|
||||
# b = np.concatenate([b, np.zeros(2, float)]) # DC rows → 0
|
||||
|
||||
# ---- QR solve for x = [c_H (N); c_w (N+1)] ----
|
||||
A = np.vstack(A_blocks).astype(float)
|
||||
Q, R = np.linalg.qr(A, mode="reduced")
|
||||
|
||||
if self.fit_constant:
|
||||
Q2 = Q[:,Phi.shape[1] * self.ports**2:]
|
||||
R22 = R[Phi.shape[1] * self.ports**2:,Phi.shape[1] * self.ports**2:]
|
||||
else:
|
||||
Q2 = Q[:,Phi.shape[1] * self.ports**2:]
|
||||
R22 = R[Phi.shape[1] * self.ports**2:,Phi.shape[1] * self.ports**2:]
|
||||
|
||||
x = np.linalg.solve(R22, Q2.T @ b)
|
||||
|
||||
# diagnostics
|
||||
resid = Q2 @ R22 @ x - b
|
||||
# self.least_squares_rms_error = float(np.sqrt(np.mean(resid**2)))
|
||||
# self.least_squares_condition = float(np.linalg.cond(R))
|
||||
self.least_squares_condition,\
|
||||
self.least_squares_row_condition,\
|
||||
self.least_squares_col_condition,\
|
||||
self.least_squares_rms_error = self.least_squares_metric(A, b)
|
||||
|
||||
return self.extract_C_d_e(x,N,d0)
|
||||
|
||||
def extract_C_d_e(self,C,N,d0=1.0):
|
||||
a = np.sqrt(2 * np.real(-np.array(self.poles)))
|
||||
if self.fit_proportional and self.fit_constant:
|
||||
d = C[1]
|
||||
e = C[0]
|
||||
C = a * C[2:]
|
||||
return C.reshape(1, -1), d, e
|
||||
elif self.fit_proportional and not self.fit_constant:
|
||||
d = 0.0
|
||||
e = C[0]
|
||||
C = a * C[1:]
|
||||
return C.reshape(1, -1), d, e
|
||||
elif not self.fit_proportional and self.fit_constant:
|
||||
d = C[0]
|
||||
e = 0.0
|
||||
C = a * C[1:]
|
||||
return C.reshape(1, -1), d, e
|
||||
else:
|
||||
C = a * C
|
||||
return C.reshape(1, -1), d0, 0.0
|
||||
|
||||
|
||||
def non_bias_Cr(self,w0):
|
||||
A = np.asarray(self.Phi)
|
||||
den = np.diag((w0 + self.Phi @ self.residuals.T).ravel())
|
||||
Cr = []
|
||||
for i in range(self.ports):
|
||||
Cr.append([])
|
||||
for j in range(self.ports):
|
||||
b = np.asarray(den) @ self.H[:,i,j].reshape(-1,1)
|
||||
Cr_ij, residuals, rank, s = np.linalg.lstsq(A, b, rcond=None)
|
||||
Cr[i].append(Cr_ij)
|
||||
return Cr
|
||||
|
||||
def get_model_responses(self,freqs):
|
||||
H = np.zeros((len(freqs),self.ports,self.ports),dtype=complex)
|
||||
s = 1j * 2*np.pi * np.asarray(freqs, float).ravel()
|
||||
phi = self.generate_basis(s, self.poles)
|
||||
den = self.w0 + phi @ self.residuals.T
|
||||
if self.Cr is None:
|
||||
self.Cr = self.non_bias_Cr(w0=self.w0)
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
num = phi @ self.Cr[i][j]
|
||||
H[:,i,j] = (num / den).reshape(1,-1)
|
||||
return H
|
||||
|
||||
class VFUtils():
|
||||
def __init__(self,npoles_cplx,freqs,H,model=MultiPortOrthonormalBasis,iterations:int=5):
|
||||
poles = generate_starting_poles(npoles_cplx,beta_min=1e4,beta_max=freqs[-1]*1.1)
|
||||
self.model=model(H=H,freqs=freqs,poles=poles)
|
||||
self.freqs=freqs
|
||||
self.H=H
|
||||
self.iterations=iterations
|
||||
|
||||
self.nports = H.shape[1]
|
||||
|
||||
self.least_squares_condition = []
|
||||
self.least_squares_row_condition = []
|
||||
self.least_squares_col_condition = []
|
||||
self.least_squares_rms_error = []
|
||||
self.eigenval_condition = []
|
||||
self.eigenval_row_condition = []
|
||||
self.eigenval_col_condition = []
|
||||
self.eigenval_rms_error = []
|
||||
|
||||
self.model_responses_freqs = None
|
||||
self.model_responses_H = None
|
||||
|
||||
def fit(self):
|
||||
for i in range(self.iterations):
|
||||
print(f"Iteration {i+1}/{self.iterations}")
|
||||
poles = self.model.next_poles
|
||||
weights = self.model.Dt
|
||||
self.model = self.model.__class__(H=self.H,freqs=self.freqs,poles=poles,weights=weights)
|
||||
print("A:",self.model.A)
|
||||
print("B:",self.model.B)
|
||||
print("C:",self.model.C)
|
||||
print("D:",self.model.D)
|
||||
print("next_pozles:",self.model.next_poles)
|
||||
print("Dt:",self.model.Dt)
|
||||
print("Dt/Dt_1:",np.linalg.norm(self.model.Dt_Dt_1))
|
||||
self.least_squares_condition.append(self.model.least_squares_condition)
|
||||
self.least_squares_row_condition.append(self.model.least_squares_row_condition)
|
||||
self.least_squares_col_condition.append(self.model.least_squares_col_condition)
|
||||
self.least_squares_rms_error.append(self.model.least_squares_rms_error)
|
||||
self.eigenval_condition.append(self.model.eigenval_condition)
|
||||
self.eigenval_row_condition.append(self.model.eigenval_row_condition)
|
||||
self.eigenval_col_condition.append(self.model.eigenval_col_condition)
|
||||
self.eigenval_rms_error.append(self.model.eigenval_rms_error)
|
||||
return self.model
|
||||
|
||||
def plot_metrics(self):
|
||||
plt.figure(figsize=(16, 12))
|
||||
plt.subplot(4, 2, 1)
|
||||
plt.plot(self.least_squares_condition, label='Least Squares Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 2)
|
||||
plt.plot(self.least_squares_row_condition, label='Least Squares Row Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 3)
|
||||
plt.plot(self.least_squares_col_condition, label='Least Squares Col Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 4)
|
||||
plt.plot(self.least_squares_rms_error, label='Least Squares RMS Error')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 5)
|
||||
plt.plot(self.eigenval_condition, label='Eigenvalue Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 6)
|
||||
plt.plot(self.eigenval_row_condition, label='Eigenvalue Row Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 7)
|
||||
plt.plot(self.eigenval_col_condition, label='Eigenvalue Col Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 8)
|
||||
plt.plot(self.eigenval_rms_error, label='Eigenvalue RMS Error')
|
||||
plt.legend()
|
||||
plt.savefig("fit_metrics.png")
|
||||
|
||||
def plot_model_responses(self):
|
||||
assert self.model_responses_freqs is not None and self.model_responses_H is not None, "Please run get_model_responses() first."
|
||||
for i in range(self.nports):
|
||||
for j in range(self.nports):
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.subplot(2, 2, 1)
|
||||
plt.plot(self.freqs, np.abs(self.H[:,i,j]), 'o', ms=4, color='red', label='Input Samples')
|
||||
plt.plot(self.model_responses_freqs, np.abs(self.model_responses_H[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
plt.title(f"Response i={i+1}, j={j+1}")
|
||||
plt.ylabel("Magnitude")
|
||||
plt.legend(loc="best")
|
||||
plt.subplot(2, 2, 2)
|
||||
plt.plot(self.freqs, np.angle(self.H[:,i,j],deg=True), 'o', ms=4, color='red', label='Input Samples')
|
||||
plt.plot(self.model_responses_freqs, np.angle(self.model_responses_H[:,i,j],deg=True), '-', lw=2, color='k', label='Fit')
|
||||
plt.title(f"Response i={i+1}, j={j+1}")
|
||||
plt.ylabel("Phase (deg)")
|
||||
plt.legend(loc="best")
|
||||
plt.tight_layout()
|
||||
plt.subplot(2, 2, 3)
|
||||
plt.plot(self.freqs, np.real(self.H[:,i,j]), 'o', ms=4, color='red', label='Input Samples')
|
||||
plt.plot(self.model_responses_freqs, np.real(self.model_responses_H[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
plt.title(f"Response i={i+1}, j={j+1}")
|
||||
plt.ylabel("Real Part")
|
||||
plt.legend(loc="best")
|
||||
plt.subplot(2, 2, 4)
|
||||
plt.plot(self.freqs, np.imag(self.H[:,i,j]), 'o', ms=4, color='red', label='Input Samples')
|
||||
plt.plot(self.model_responses_freqs, np.imag(self.model_responses_H[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
plt.title(f"Response i={i+1}, j={j+1}")
|
||||
plt.ylabel("Imag Part")
|
||||
plt.legend(loc="best")
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"model_response_{i+1}{j+1}.png")
|
||||
print(f"Saved model_response_{i+1}{j+1}.png")
|
||||
|
||||
def get_model_responses(self,freqs):
|
||||
self.model_responses_freqs = freqs
|
||||
self.model_responses_H = self.model.get_model_responses(freqs)
|
||||
return self.model_responses_H
|
||||
|
||||
|
||||
def noise(n:complex,coeff:float=0.05):
|
||||
noise_r = rnd.gauss(-coeff * n.real, coeff * n.real)
|
||||
noise_i = rnd.gauss(-coeff * n.imag, coeff * n.imag)
|
||||
return complex(n.real + noise_r, n.imag + noise_i)
|
||||
|
||||
if __name__ == "__main__":
|
||||
start_point = 0
|
||||
id = 3000
|
||||
network = rf.Network(f"/tmp/paramer/simulation/{id}/{id}.s2p")
|
||||
# network = rf.data.ring_slot
|
||||
ports = network.nports
|
||||
K = 100
|
||||
|
||||
full_freqences = network.f[start_point:]
|
||||
noised_sampled_points = network.y[start_point:,:,:].reshape(-1,ports,ports)
|
||||
sampled_points = network.y[start_point:,:,:].reshape(-1,ports,ports)
|
||||
|
||||
# noised_sampled_points = network.y[start_point:,0,0].reshape(-1,1,1)
|
||||
# sampled_points = network.y[start_point:,0,0].reshape(-1,1,1)
|
||||
|
||||
H,freqs = auto_select_multple_ports(noised_sampled_points,full_freqences,max_points=20)
|
||||
poles = generate_starting_poles(2,beta_min=1e4,beta_max=freqs[-1]*1.1)
|
||||
|
||||
vf = VFUtils(npoles_cplx=2,freqs=freqs,H=H,model=MultiPortOrthonormalBasis,iterations=K)
|
||||
model = vf.fit()
|
||||
vf.plot_metrics()
|
||||
model_responses = vf.get_model_responses(full_freqences)
|
||||
vf.plot_model_responses()
|
||||
|
||||
# # Original plot functions
|
||||
|
||||
# Dt_1 = np.ones((len(freqs),1),np.complex128)
|
||||
# # Levi step (no weighting):
|
||||
# basis = MultiPortOrthonormalBasis(H,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.C)
|
||||
# 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 = MultiPortOrthonormalBasis(H,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.C)
|
||||
# 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])
|
||||
# H_evaluated = basis.get_model_responses(full_freqences)
|
||||
# fitted_points = H_evaluated
|
||||
# sliced_freqences = freqs
|
||||
|
||||
# input_points = H
|
||||
# for i in range(ports):
|
||||
# for j in range(ports):
|
||||
# fig, axes = plt.subplots(3, 2, figsize=(15, 16), sharex=False)
|
||||
# ax00 = axes[0][0]
|
||||
# ax00.plot(full_freqences, np.abs(sampled_points[:,i,j]), 'o', ms=4, color='red', label='Samples')
|
||||
# ax00.plot(full_freqences, np.abs(fitted_points[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
# ax00.plot(sliced_freqences, np.abs(input_points[:,i,j]), 'x', ms=4, color='blue', label='Input Samples')
|
||||
# ax00.set_title(f"Response i={i+1}, j={j+1}")
|
||||
# ax00.set_ylabel("Magnitude")
|
||||
# ax00.legend(loc="best")
|
||||
|
||||
# ax01 = axes[0][1]
|
||||
# ax01.set_title(f"Response i={i+1}, j={j+1}")
|
||||
# ax01.set_ylabel("Phase (deg)")
|
||||
# ax01.plot(full_freqences, np.angle(sampled_points[:,i,j],deg=True), 'o', ms=4, color='red', label='Samples')
|
||||
# ax01.plot(full_freqences, np.angle(fitted_points[:,i,j],deg=True), '-', lw=2, color='k', label='Fit')
|
||||
# ax01.plot(sliced_freqences, np.angle(input_points[:,i,j],deg=True), 'x', ms=4, color='blue', label='Input Samples')
|
||||
# ax01.legend(loc="best")
|
||||
|
||||
# # ax00 = axes[0][0]
|
||||
# # ax00.plot(full_freqences, np.real(sampled_points[:,i,j]), 'o', ms=4, color='red', label='Samples')
|
||||
# # ax00.plot(full_freqences, np.real(fitted_points[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
# # ax00.plot(sliced_freqences, np.real(input_points[:,i,j]), 'x', ms=4, color='blue', label='Input Samples')
|
||||
# # ax00.set_title(f"Response i={i+1}, j={j+1}")
|
||||
# # ax00.set_ylabel("Real Part")
|
||||
# # ax00.legend(loc="best")
|
||||
|
||||
# # ax01 = axes[0][1]
|
||||
# # ax01.set_title(f"Response i={i+1}, j={j+1}")
|
||||
# # ax01.set_ylabel("Imag Part")
|
||||
# # ax01.plot(full_freqences, np.imag(sampled_points[:,i,j]), 'o', ms=4, color='red', label='Samples')
|
||||
# # ax01.plot(full_freqences, np.imag(fitted_points[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
# # ax01.plot(sliced_freqences, np.imag(input_points[:,i,j]), 'x', ms=4, color='blue', label='Input Samples')
|
||||
# # ax01.legend(loc="best")
|
||||
|
||||
# ax10 = axes[1][0]
|
||||
# ax10.plot(least_squares_condition, label='Least Squares Condition')
|
||||
# ax10.set_title("least_squares_condition")
|
||||
# ax10.set_ylabel("Magnitude")
|
||||
# ax10.legend(loc="best")
|
||||
|
||||
# ax11 = axes[1][1]
|
||||
# ax11.plot(least_squares_rms_error, label='Least Squares RMS Error')
|
||||
# ax11.set_title("least_squares_rms_error")
|
||||
# ax11.set_ylabel("Magnitude")
|
||||
# ax11.legend(loc="best")
|
||||
|
||||
# ax20 = axes[2][0]
|
||||
# ax20.plot(eigenval_condition, label='Eigenvalue Condition')
|
||||
# ax20.set_title("eigenval_condition")
|
||||
# ax20.set_ylabel("Magnitude")
|
||||
# ax20.legend(loc="best")
|
||||
|
||||
# ax21 = axes[2][1]
|
||||
# ax21.plot(eigenval_rms_error, label='Eigenvalue RMS Error')
|
||||
# ax21.set_title("eigenval_rms_error")
|
||||
# ax21.set_ylabel("Magnitude")
|
||||
# ax21.legend(loc="best")
|
||||
# fig.tight_layout()
|
||||
# plt.savefig(f"MultiplePortQR_port_{i+1}{j+1}.png")
|
||||
# print(f"Saved MultiplePortQR_port_{i+1}{j+1}.png")
|
||||
|
||||
|
||||
|
||||
|
||||
176
core/VFManager.py
Normal file
176
core/VFManager.py
Normal file
@@ -0,0 +1,176 @@
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from .basis.MultiPortOrthonormalBasis import MultiPortOrthonormalBasis
|
||||
from .utils import generate_starting_poles
|
||||
|
||||
class VFManager():
|
||||
def __init__(
|
||||
self,
|
||||
npoles_cplx,
|
||||
freqs,
|
||||
H,
|
||||
model=MultiPortOrthonormalBasis,
|
||||
iterations:int=5,
|
||||
fit_constant:bool=True,
|
||||
fit_proportional:bool=False,
|
||||
dc_enforce:bool=False,
|
||||
passivity_enforce:bool=True,
|
||||
verbose:bool=True
|
||||
):
|
||||
|
||||
|
||||
self.freqs=freqs
|
||||
self.H=H
|
||||
self.iterations=iterations
|
||||
self.fit_constant=fit_constant
|
||||
self.fit_proportional=fit_proportional
|
||||
self.dc_enforce=dc_enforce
|
||||
self.passivity_enforce=passivity_enforce
|
||||
self.verbose=verbose
|
||||
|
||||
self.nports = H.shape[1]
|
||||
self.npoles_cplx = npoles_cplx
|
||||
|
||||
self.least_squares_condition = []
|
||||
self.least_squares_row_condition = []
|
||||
self.least_squares_col_condition = []
|
||||
self.least_squares_rms_error = []
|
||||
self.eigenval_condition = []
|
||||
self.eigenval_row_condition = []
|
||||
self.eigenval_col_condition = []
|
||||
self.eigenval_rms_error = []
|
||||
|
||||
self.model_instance = None
|
||||
self.model_responses_freqs = None
|
||||
self.model_responses_H = None
|
||||
|
||||
self.model=model
|
||||
|
||||
def fit(self):
|
||||
self.levi()
|
||||
self.model_instance = self.sk_iteration()
|
||||
return self.model
|
||||
|
||||
def levi(self):
|
||||
self.poles = generate_starting_poles(self.npoles_cplx,beta_min=1e4,beta_max=self.freqs[-1]*1.1)
|
||||
self.model_instance=self.model(
|
||||
H=self.H,
|
||||
freqs=self.freqs,
|
||||
poles=self.poles,
|
||||
fit_constant=self.fit_constant,
|
||||
fit_proportional=self.fit_proportional,
|
||||
dc_enforce=self.dc_enforce,
|
||||
passivity_enforce=self.passivity_enforce
|
||||
)
|
||||
return self.model_instance
|
||||
|
||||
def sk_iteration(self):
|
||||
for i in range(self.iterations):
|
||||
assert self.model_instance is not None ,"Please run levi() first."
|
||||
self.poles = self.model_instance.next_poles
|
||||
self.weights = self.model_instance.Dt
|
||||
self.model_instance = self.model(
|
||||
H=self.H,
|
||||
freqs=self.freqs,
|
||||
poles=self.poles,
|
||||
weights=self.weights,
|
||||
fit_constant=self.fit_constant,
|
||||
fit_proportional=self.fit_proportional,
|
||||
dc_enforce=self.dc_enforce,
|
||||
passivity_enforce=self.passivity_enforce
|
||||
)
|
||||
if self.verbose:
|
||||
print(f"Iteration {i+1}/{self.iterations}")
|
||||
print("A:",self.model_instance.A)
|
||||
print("B:",self.model_instance.B)
|
||||
print("C:",self.model_instance.C)
|
||||
print("D:",self.model_instance.D)
|
||||
print("next_pozles:",self.model_instance.next_poles)
|
||||
print("Dt:",self.model_instance.Dt)
|
||||
print("Dt/Dt_1:",np.linalg.norm(self.model_instance.Dt_Dt_1))
|
||||
self.least_squares_condition.append(self.model_instance.least_squares_condition)
|
||||
self.least_squares_row_condition.append(self.model_instance.least_squares_row_condition)
|
||||
self.least_squares_col_condition.append(self.model_instance.least_squares_col_condition)
|
||||
self.least_squares_rms_error.append(self.model_instance.least_squares_rms_error)
|
||||
self.eigenval_condition.append(self.model_instance.eigenval_condition)
|
||||
self.eigenval_row_condition.append(self.model_instance.eigenval_row_condition)
|
||||
self.eigenval_col_condition.append(self.model_instance.eigenval_col_condition)
|
||||
self.eigenval_rms_error.append(self.model_instance.eigenval_rms_error)
|
||||
return self.model_instance
|
||||
|
||||
def plot_metrics(self,show:bool=True,save_path=None):
|
||||
plt.figure(figsize=(16, 12))
|
||||
plt.subplot(4, 2, 1)
|
||||
plt.plot(self.least_squares_condition, label='Least Squares Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 2)
|
||||
plt.plot(self.least_squares_row_condition, label='Least Squares Row Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 3)
|
||||
plt.plot(self.least_squares_col_condition, label='Least Squares Col Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 4)
|
||||
plt.plot(self.least_squares_rms_error, label='Least Squares RMS Error')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 5)
|
||||
plt.plot(self.eigenval_condition, label='Eigenvalue Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 6)
|
||||
plt.plot(self.eigenval_row_condition, label='Eigenvalue Row Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 7)
|
||||
plt.plot(self.eigenval_col_condition, label='Eigenvalue Col Condition')
|
||||
plt.legend()
|
||||
plt.subplot(4, 2, 8)
|
||||
plt.plot(self.eigenval_rms_error, label='Eigenvalue RMS Error')
|
||||
plt.legend()
|
||||
if show:
|
||||
plt.show()
|
||||
if save_path is not None:
|
||||
if self.verbose:
|
||||
print(f"Saving metrics plot to {save_path}/fitting_metrics.png")
|
||||
plt.savefig(f"{save_path}/fitting_metrics.png")
|
||||
|
||||
def plot_model_responses(self,show:bool=True,save_path=None):
|
||||
assert self.model_responses_freqs is not None and self.model_responses_H is not None, "Please run get_model_responses() first."
|
||||
for i in range(self.nports):
|
||||
for j in range(self.nports):
|
||||
plt.figure(figsize=(12, 6))
|
||||
plt.subplot(2, 2, 1)
|
||||
plt.plot(self.freqs, np.abs(self.H[:,i,j]), 'o', ms=4, color='red', label='Input Samples')
|
||||
plt.plot(self.model_responses_freqs, np.abs(self.model_responses_H[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
plt.title(f"Response i={i+1}, j={j+1}")
|
||||
plt.ylabel("Magnitude")
|
||||
plt.legend(loc="best")
|
||||
plt.subplot(2, 2, 2)
|
||||
plt.plot(self.freqs, np.angle(self.H[:,i,j],deg=True), 'o', ms=4, color='red', label='Input Samples')
|
||||
plt.plot(self.model_responses_freqs, np.angle(self.model_responses_H[:,i,j],deg=True), '-', lw=2, color='k', label='Fit')
|
||||
plt.title(f"Response i={i+1}, j={j+1}")
|
||||
plt.ylabel("Phase (deg)")
|
||||
plt.legend(loc="best")
|
||||
plt.tight_layout()
|
||||
plt.subplot(2, 2, 3)
|
||||
plt.plot(self.freqs, np.real(self.H[:,i,j]), 'o', ms=4, color='red', label='Input Samples')
|
||||
plt.plot(self.model_responses_freqs, np.real(self.model_responses_H[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
plt.title(f"Response i={i+1}, j={j+1}")
|
||||
plt.ylabel("Real Part")
|
||||
plt.legend(loc="best")
|
||||
plt.subplot(2, 2, 4)
|
||||
plt.plot(self.freqs, np.imag(self.H[:,i,j]), 'o', ms=4, color='red', label='Input Samples')
|
||||
plt.plot(self.model_responses_freqs, np.imag(self.model_responses_H[:,i,j]), '-', lw=2, color='k', label='Fit')
|
||||
plt.title(f"Response i={i+1}, j={j+1}")
|
||||
plt.ylabel("Imag Part")
|
||||
plt.legend(loc="best")
|
||||
plt.tight_layout()
|
||||
if show:
|
||||
plt.show()
|
||||
if save_path is not None:
|
||||
if self.verbose:
|
||||
print(f"Saving response plot for port {i+1},{j+1} to {save_path}/response_{i+1}_{j+1}.png")
|
||||
plt.savefig(f"{save_path}/response_{i+1}_{j+1}.png")
|
||||
|
||||
def get_model_responses(self,freqs):
|
||||
assert self.model_instance is not None ,"Please run levi() and sk_iteration() first."
|
||||
self.model_responses_freqs = freqs
|
||||
self.model_responses_H = self.model_instance.get_model_responses(freqs)
|
||||
return self.model_responses_H
|
||||
362
core/basis/MultiPortOrthonormalBasis.py
Normal file
362
core/basis/MultiPortOrthonormalBasis.py
Normal file
@@ -0,0 +1,362 @@
|
||||
import numpy as np
|
||||
import skrf as rf
|
||||
from ..utils import cond_row_inf, cond_col_one, generate_starting_poles
|
||||
|
||||
|
||||
class MultiPortOrthonormalBasis:
|
||||
def __init__(self,H,freqs,poles,weights=None,passivity_enforce=True,dc_enforce=True,fit_constant=True,fit_proportional=False):
|
||||
self.least_squares_condition = None
|
||||
self.least_squares_row_condition = None
|
||||
self.least_squares_col_condition = None
|
||||
self.least_squares_rms_error = None
|
||||
self.eigenval_condition = None
|
||||
self.eigenval_row_condition = None
|
||||
self.eigenval_col_condition = None
|
||||
self.eigenval_rms_error = None
|
||||
self.Cr = None
|
||||
|
||||
self.dc_tol = 1e-18
|
||||
|
||||
self.dc_enforce = dc_enforce
|
||||
self.fit_constant = fit_constant
|
||||
self.fit_proportional = fit_proportional
|
||||
|
||||
self.freqs = freqs
|
||||
self.H = H
|
||||
self.ports = H.shape[1]
|
||||
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.C,self.w0,self.e = self.fit_denominator(self.H, weights=weights)
|
||||
self.D = self.w0
|
||||
|
||||
self.residuals = self.C / np.sqrt(2 * np.real(-np.array(self.poles)))
|
||||
|
||||
z = np.linalg.eigvals(self.A - self.B @ self.C)
|
||||
|
||||
if passivity_enforce:
|
||||
self.next_poles = self.passivity_enforce(z)
|
||||
else:
|
||||
self.next_poles = z
|
||||
|
||||
self.eigenval_condition,\
|
||||
self.eigenval_row_condition,\
|
||||
self.eigenval_col_condition,\
|
||||
self.eigenval_rms_error = self.eigen_metric()
|
||||
|
||||
self.Dt = self.eval_Dt_state_space()
|
||||
self.Dt_Dt_1 = np.linalg.norm(self.Dt) / np.linalg.norm(weights) if weights is not None else np.linalg.norm(self.Dt)
|
||||
pass
|
||||
|
||||
def eigen_metric(self):
|
||||
|
||||
"""Return condition number and RMS error of eigenvalues of A-BC."""
|
||||
z = np.linalg.eigvals(self.A - self.B @ self.C)
|
||||
cond = np.linalg.cond(self.A - self.B @ self.C)
|
||||
rms = np.sqrt(np.mean(np.abs(np.real(z) - np.real(self.poles))**2 + np.abs(np.imag(z) - np.imag(self.poles))**2))
|
||||
|
||||
row_cond = cond_row_inf(self.A - self.B @ self.C)
|
||||
col_cond = cond_col_one(self.A - self.B @ self.C)
|
||||
|
||||
return cond,row_cond,col_cond,rms
|
||||
|
||||
def least_squares_metric(self,A,b):
|
||||
"""Return condition number and RMS error of least-squares matrix A and rhs b."""
|
||||
cond = np.linalg.cond(A)
|
||||
rms = np.sqrt(np.mean((A @ np.linalg.pinv(A) @ b - b)**2))
|
||||
|
||||
row_cond = cond_row_inf(A)
|
||||
col_cond = cond_col_one(A)
|
||||
|
||||
return cond,row_cond,col_cond,rms
|
||||
|
||||
|
||||
def passivity_enforce(self,poles):
|
||||
"""enforce poles' real parts to be negative"""
|
||||
enforced_poles = []
|
||||
for pole in poles:
|
||||
if pole.real > 0:
|
||||
pole = -np.conj(pole)
|
||||
enforced_poles.append(pole)
|
||||
return enforced_poles
|
||||
|
||||
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, float); n = A.shape[0]
|
||||
B = np.asarray(self.B, float).reshape(n, 1)
|
||||
C = np.asarray(self.C, float).reshape(1, n)
|
||||
D = self.D
|
||||
I = np.eye(n, dtype=float)
|
||||
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."""
|
||||
def all_pass(s,ap_list):
|
||||
res = 1.0 +0.0j
|
||||
for ap in ap_list:
|
||||
res *= (s - np.conj(ap)) / (s + ap)
|
||||
return res
|
||||
|
||||
cols = []
|
||||
ap_list = []
|
||||
i = 0
|
||||
while i < len(poles):
|
||||
ap = -poles[i]
|
||||
if ap.real < 0:
|
||||
raise ValueError("poles must be in the LHP")
|
||||
if i+1 < len(poles) and np.isclose(poles[i+1], np.conj(-ap)):
|
||||
ap1 = - poles[i+1]
|
||||
phi1 = np.sqrt(2 * ap.real) * all_pass(s, ap_list) * ((s - np.abs(ap))/((s + ap)*(s + ap1)))
|
||||
phi2 = np.sqrt(2 * ap.real) * all_pass(s, ap_list) * ((s + np.abs(ap))/((s + ap)*(s + ap1)))
|
||||
cols += [phi1, phi2]
|
||||
i += 2
|
||||
ap_list.append(ap)
|
||||
ap_list.append(ap1)
|
||||
else:
|
||||
basis = np.sqrt(2 * ap.real) * all_pass(s, ap_list) * (1/(s + ap))
|
||||
cols.append(basis)
|
||||
i += 1
|
||||
ap_list.append(ap)
|
||||
Phi = np.column_stack(cols).astype(np.complex128)
|
||||
return Phi
|
||||
|
||||
def matrix_A(self, poles):
|
||||
def A_col(p:np.complex128,index:int):
|
||||
ap = -p
|
||||
if abs(ap.imag) < 1e-14:
|
||||
col = []
|
||||
for i in range(index):
|
||||
col.append(0.0)
|
||||
col.append(-ap.real)
|
||||
for i in range(len(poles)-index-1):
|
||||
col.append(2*(-ap).real)
|
||||
return np.array([col], float)
|
||||
else:
|
||||
col1 = []
|
||||
col2 = []
|
||||
for i in range(index):
|
||||
col1.append(0.0)
|
||||
col2.append(0.0)
|
||||
col1.append(-ap.real); col2.append(-ap.real - np.abs(ap))
|
||||
col1.append(-ap.real + np.abs(ap)); col2.append(-ap.real)
|
||||
for i in range(len(poles)-index-2):
|
||||
col1.append(2*(-ap).real)
|
||||
col2.append(2*(-ap).real)
|
||||
return np.array([col1, col2], float)
|
||||
|
||||
i = 0
|
||||
cols = []
|
||||
while i < len(poles):
|
||||
p = poles[i]
|
||||
cols.extend(A_col(p,i))
|
||||
if i+1 < len(poles) and np.isclose(poles[i+1], np.conj(p)): i += 2
|
||||
else: i += 1
|
||||
A = np.column_stack(cols).astype(float)
|
||||
return A
|
||||
|
||||
def vector_B(self, poles):
|
||||
return np.ones((len(poles), 1), float)
|
||||
|
||||
def fit_denominator(self, H, weights=None, d0 = 1.0):
|
||||
"""
|
||||
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|.
|
||||
"""
|
||||
K, N = self.Phi.shape
|
||||
one = np.ones((K, 1), np.complex128)
|
||||
Phi = self.Phi
|
||||
dc_tol = 1e-18
|
||||
has_dc = self.dc_enforce and self.freqs[0] < dc_tol
|
||||
keep = np.ones(K, dtype=bool)
|
||||
|
||||
# SK weighting (applied only to the (73) rows we keep in LS)
|
||||
|
||||
if has_dc:
|
||||
# Enforce DC response exactly:
|
||||
k0 = int(np.argmin(np.abs(self.freqs)))
|
||||
keep[k0] = False
|
||||
|
||||
if self.fit_constant:
|
||||
Phi_w = np.hstack([one, Phi])
|
||||
index = 0
|
||||
M_kp = None
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
M0 = np.zeros((K,N*self.ports**2),dtype=complex)
|
||||
M0[:,index*N:(index+1)*N] = Phi
|
||||
M0 = np.hstack([M0, -(H[:,i,j].reshape(-1,1) * Phi_w)]).reshape((K, -1))[keep,:] # (K, 2N), complex
|
||||
index+=1
|
||||
M_kp = M0 if M_kp is None else np.vstack([M_kp, M0])
|
||||
assert M_kp is not None
|
||||
else:
|
||||
index = 0
|
||||
M_kp = None
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
M0 = np.zeros((K,N*self.ports**2),dtype=complex)
|
||||
M0[:,index*N:(index+1)*N] = Phi
|
||||
M0 = np.hstack([M0, -(H[:,i,j].reshape(-1,1) * Phi)]).reshape((K, -1))[keep,:] # (K, 2N), complex
|
||||
index+=1
|
||||
M_kp = M0 if M_kp is None else np.vstack([M_kp, M0])
|
||||
assert M_kp is not None
|
||||
|
||||
if weights is None:
|
||||
weights_kp = np.diag(np.ones(len(self.freqs[keep]) * self.ports**2, np.complex128))
|
||||
else:
|
||||
weights_kp = np.diag(np.ones(len(self.freqs[keep]) * self.ports**2, np.complex128))
|
||||
# weights_kp0 = weights[keep]
|
||||
# weights0 = []
|
||||
# for i in range(self.ports **2 ):
|
||||
# for res in weights_kp0:
|
||||
# weights0.append(1/res)
|
||||
# weights_kp = np.diag(np.array(weights0))
|
||||
|
||||
|
||||
if has_dc:
|
||||
M_w_kp = weights_kp @ M_kp
|
||||
A_re = np.real(M_w_kp)
|
||||
A_im = np.imag(M_w_kp)
|
||||
mask = np.ones(K, dtype=bool); mask[k0] = False
|
||||
# exact (unweighted) DC rows:
|
||||
# A_dc_re = np.real(M_kp).reshape(1, -1)
|
||||
# A_dc_im = np.imag(M_kp).reshape(1, -1)
|
||||
else:
|
||||
M_w_kp = weights_kp @ M_kp
|
||||
A_re = np.real(M_w_kp)
|
||||
A_im = np.imag(M_w_kp)
|
||||
# A_dc_re = A_dc_im = None
|
||||
|
||||
A_blocks = [A_re, A_im]
|
||||
|
||||
if self.fit_constant:
|
||||
Hk_sum = []
|
||||
for i in range(self.ports):
|
||||
Hk_sum.append([])
|
||||
for j in range(self.ports):
|
||||
Hk_kp0 = H[:,i,j][keep]
|
||||
Hk_sum[i].append(np.sum(np.abs(Hk_kp0)**2))
|
||||
# Hk_kp = Hk_kp0 if Hk_kp is None else np.hstack([Hk_kp, Hk_kp0])
|
||||
K_keep = int(np.count_nonzero(keep))
|
||||
A_w0 = []
|
||||
b_w0 = []
|
||||
# Hk_sum = np.sum(np.abs(Hk_kp)**2)
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
beta_ij = float(np.sqrt(Hk_sum[i][j]))
|
||||
mean_row = (beta_ij / K_keep) * np.sum(Phi_w[keep, :], axis=0)
|
||||
A_w0.append(np.concatenate([np.zeros(N*self.ports**2, float),
|
||||
np.real(mean_row).astype(float)]
|
||||
).reshape(1, -1))
|
||||
b_w0.append(np.array([beta_ij], float))
|
||||
b_w0 = np.asarray(b_w0).ravel()
|
||||
|
||||
A_blocks += A_w0
|
||||
m = A_re.shape[0] + A_im.shape[0]
|
||||
b = np.zeros(m, float)
|
||||
b = np.concatenate([b, b_w0])
|
||||
else:
|
||||
H_kp = None
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
H_0 = H[:,i,j][keep]
|
||||
H_kp = H_0 if H_kp is None else np.hstack([H_kp, H_0])
|
||||
assert H_kp is not None
|
||||
H_kp = weights_kp @ H_kp.reshape(-1,1)
|
||||
|
||||
b_re = np.real(d0 * H_kp)
|
||||
b_im = np.imag(d0 * H_kp)
|
||||
b = np.concatenate([b_re.ravel(), b_im.ravel()]).astype(float)
|
||||
|
||||
# ---- build final stacked-real system ----
|
||||
|
||||
|
||||
# if A_dc_re is not None:
|
||||
# A_blocks += [A_dc_re, A_dc_im]
|
||||
# b = np.concatenate([b, np.zeros(2, float)]) # DC rows → 0
|
||||
|
||||
# ---- QR solve for x = [c_H (N); c_w (N+1)] ----
|
||||
A = np.vstack(A_blocks).astype(float)
|
||||
Q, R = np.linalg.qr(A, mode="reduced")
|
||||
|
||||
if self.fit_constant:
|
||||
Q2 = Q[:,Phi.shape[1] * self.ports**2:]
|
||||
R22 = R[Phi.shape[1] * self.ports**2:,Phi.shape[1] * self.ports**2:]
|
||||
else:
|
||||
Q2 = Q[:,Phi.shape[1] * self.ports**2:]
|
||||
R22 = R[Phi.shape[1] * self.ports**2:,Phi.shape[1] * self.ports**2:]
|
||||
|
||||
x = np.linalg.solve(R22, Q2.T @ b)
|
||||
|
||||
# diagnostics
|
||||
resid = Q2 @ R22 @ x - b
|
||||
# self.least_squares_rms_error = float(np.sqrt(np.mean(resid**2)))
|
||||
# self.least_squares_condition = float(np.linalg.cond(R))
|
||||
self.least_squares_condition,\
|
||||
self.least_squares_row_condition,\
|
||||
self.least_squares_col_condition,\
|
||||
self.least_squares_rms_error = self.least_squares_metric(A, b)
|
||||
|
||||
return self.extract_C_d_e(x,N,d0)
|
||||
|
||||
def extract_C_d_e(self,C,N,d0=1.0):
|
||||
a = np.sqrt(2 * np.real(-np.array(self.poles)))
|
||||
if self.fit_proportional and self.fit_constant:
|
||||
d = C[1]
|
||||
e = C[0]
|
||||
C = a * C[2:]
|
||||
return C.reshape(1, -1), d, e
|
||||
elif self.fit_proportional and not self.fit_constant:
|
||||
d = 0.0
|
||||
e = C[0]
|
||||
C = a * C[1:]
|
||||
return C.reshape(1, -1), d, e
|
||||
elif not self.fit_proportional and self.fit_constant:
|
||||
d = C[0]
|
||||
e = 0.0
|
||||
C = a * C[1:]
|
||||
return C.reshape(1, -1), d, e
|
||||
else:
|
||||
C = a * C
|
||||
return C.reshape(1, -1), d0, 0.0
|
||||
|
||||
|
||||
def non_bias_Cr(self,w0):
|
||||
A = np.asarray(self.Phi)
|
||||
den = np.diag((w0 + self.Phi @ self.residuals.T).ravel())
|
||||
Cr = []
|
||||
for i in range(self.ports):
|
||||
Cr.append([])
|
||||
for j in range(self.ports):
|
||||
b = np.asarray(den) @ self.H[:,i,j].reshape(-1,1)
|
||||
Cr_ij, residuals, rank, s = np.linalg.lstsq(A, b, rcond=None)
|
||||
Cr[i].append(Cr_ij)
|
||||
return Cr
|
||||
|
||||
def get_model_responses(self,freqs):
|
||||
H = np.zeros((len(freqs),self.ports,self.ports),dtype=complex)
|
||||
s = 1j * 2*np.pi * np.asarray(freqs, float).ravel()
|
||||
phi = self.generate_basis(s, self.poles)
|
||||
den = self.w0 + phi @ self.residuals.T
|
||||
if self.Cr is None:
|
||||
self.Cr = self.non_bias_Cr(w0=self.w0)
|
||||
for i in range(self.ports):
|
||||
for j in range(self.ports):
|
||||
num = phi @ self.Cr[i][j]
|
||||
H[:,i,j] = (num / den).reshape(1,-1)
|
||||
return H
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import numpy as np
|
||||
from core.sk_iter import generate_starting_poles
|
||||
from core.utils import generate_starting_poles
|
||||
from scipy.linalg import block_diag
|
||||
import skrf as rf
|
||||
from skrf import VectorFitting
|
||||
from core.freqency import auto_select_multple_ports
|
||||
from core.sample import auto_select_multple_ports
|
||||
import matplotlib.pyplot as plt
|
||||
import random as rnd
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import numpy as np
|
||||
from core.sk_iter import generate_starting_poles
|
||||
from core.utils import generate_starting_poles
|
||||
from scipy.linalg import block_diag
|
||||
import skrf as rf
|
||||
from skrf import VectorFitting
|
||||
from core.freqency import auto_select
|
||||
from core.sample import auto_select
|
||||
|
||||
class BasicBasis:
|
||||
def __init__(self,H,freqs,poles,weights=None):
|
||||
@@ -1,9 +1,9 @@
|
||||
import numpy as np
|
||||
from core.sk_iter import generate_starting_poles
|
||||
from core.utils import generate_starting_poles
|
||||
from scipy.linalg import block_diag
|
||||
import skrf as rf
|
||||
from skrf import VectorFitting
|
||||
from core.freqency import auto_select
|
||||
from core.sample import auto_select
|
||||
|
||||
class BasicBasisQR:
|
||||
def __init__(self,H,freqs,poles,weights=None):
|
||||
@@ -1,9 +1,9 @@
|
||||
import numpy as np
|
||||
from core.sk_iter import generate_starting_poles
|
||||
from core.utils import generate_starting_poles
|
||||
from scipy.linalg import block_diag
|
||||
import skrf as rf
|
||||
from skrf import VectorFitting
|
||||
from core.freqency import auto_select
|
||||
from core.sample import auto_select
|
||||
import random as rnd
|
||||
|
||||
class RelaxedBasicBasisQR:
|
||||
909
core/util.py
909
core/util.py
@@ -1,909 +0,0 @@
|
||||
"""
|
||||
|
||||
.. currentmodule:: skrf.util
|
||||
========================================
|
||||
util (:mod:`skrf.util`)
|
||||
========================================
|
||||
|
||||
Holds utilities that are general conveniences.
|
||||
|
||||
|
||||
Time-related utilities
|
||||
----------------------
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
now_string
|
||||
now_string_2_dt
|
||||
|
||||
ProgressBar
|
||||
|
||||
Array-related functions
|
||||
-----------------------
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
find_nearest
|
||||
find_nearest_index
|
||||
has_duplicate_value
|
||||
smooth
|
||||
|
||||
File-related functions
|
||||
----------------------
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
get_fid
|
||||
get_extn
|
||||
basename_noext
|
||||
git_version
|
||||
unique_name
|
||||
findReplace
|
||||
dict_2_recarray
|
||||
|
||||
General Purpose Objects
|
||||
-----------------------
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
HomoList
|
||||
HomoDict
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import collections
|
||||
import contextlib
|
||||
import fnmatch
|
||||
import os
|
||||
import pprint
|
||||
import re
|
||||
import sys
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from subprocess import PIPE, Popen
|
||||
from typing import Any, Callable, Iterable, TypeVar
|
||||
|
||||
import numpy as np
|
||||
|
||||
from skrf.constants import Number
|
||||
|
||||
try:
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.axes import Axes
|
||||
from matplotlib.figure import Figure
|
||||
except ImportError:
|
||||
Figure = TypeVar("Figure")
|
||||
Axes = TypeVar("Axes")
|
||||
pass
|
||||
|
||||
def plotting_available() -> bool:
|
||||
return "matplotlib" in sys.modules
|
||||
|
||||
def partial_with_docs(func, *args1, **kwargs1):
|
||||
@wraps(func)
|
||||
def method(self, *args2, **kwargs2):
|
||||
return func(self, *args1, *args2, **kwargs1, **kwargs2)
|
||||
return method
|
||||
|
||||
def axes_kwarg(func):
|
||||
"""
|
||||
This decorator checks if a :class:`matplotlib.axes.Axes` object is passed,
|
||||
if not the current axis will be gathered through :func:`plt.gca`.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
When trying to run the decorated function without matplotlib
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
ax = kwargs.pop('ax', None)
|
||||
try:
|
||||
if ax is None:
|
||||
ax = plt.gca()
|
||||
except NameError as err:
|
||||
raise RuntimeError("Plotting is not available") from err
|
||||
func(*args, ax=ax, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
def copy_doc(copy_func: Callable) -> Callable:
|
||||
"""Use Example: copy_doc(self.copy_func)(self.func) or used as deco"""
|
||||
def wrapper(func: Callable) -> Callable:
|
||||
func.__doc__ = copy_func.__doc__
|
||||
return func
|
||||
return wrapper
|
||||
|
||||
def figure(*args, **kwargs) -> Figure:
|
||||
"""
|
||||
Wraps the matplotlib figure call and raises if not available.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
When trying to get subplots without matplotlib installed.
|
||||
"""
|
||||
|
||||
try:
|
||||
return plt.figure(*args, **kwargs)
|
||||
except NameError as err:
|
||||
raise RuntimeError("Plotting is not available") from err
|
||||
|
||||
def subplots(*args, **kwargs) -> tuple[Figure, np.ndarray]:
|
||||
"""
|
||||
Wraps the matplotlib subplots call and raises if not available.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
When trying to get subplots without matplotlib installed.
|
||||
"""
|
||||
|
||||
try:
|
||||
return plt.subplots(*args, **kwargs)
|
||||
except NameError as err:
|
||||
raise RuntimeError("Plotting is not available") from err
|
||||
|
||||
def now_string() -> str:
|
||||
"""
|
||||
Return a unique sortable string, representing the current time.
|
||||
|
||||
Nice for generating date-time stamps to be used in file-names,
|
||||
the companion function :func:`now_string_2_dt` can be used
|
||||
to read these string back into datetime objects.
|
||||
|
||||
Returns
|
||||
-------
|
||||
now : string
|
||||
curent date-time stamps.
|
||||
|
||||
See Also
|
||||
--------
|
||||
now_string_2_dt
|
||||
|
||||
"""
|
||||
return datetime.now().__str__().replace('-','.').replace(':','.').replace(' ','.')
|
||||
|
||||
|
||||
def now_string_2_dt(s: str) -> datetime:
|
||||
"""
|
||||
Converts the output of :func:`now_string` to a datetime object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
s : str
|
||||
date-time stamps string as generated by :func:`now_string`
|
||||
|
||||
Returns
|
||||
-------
|
||||
dt : datetime
|
||||
date-time stamps
|
||||
|
||||
See Also
|
||||
--------
|
||||
now_string
|
||||
|
||||
"""
|
||||
return datetime(*[int(k) for k in s.split('.')])
|
||||
|
||||
|
||||
def find_nearest(array: np.ndarray, value: Number) -> Number:
|
||||
"""
|
||||
Find the nearest value in array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
array : np.ndarray
|
||||
array we are searching for a value in
|
||||
value : element of the array
|
||||
value to search for
|
||||
|
||||
Returns
|
||||
--------
|
||||
found_value : an element of the array
|
||||
the value that is numerically closest to `value`
|
||||
|
||||
"""
|
||||
idx = find_nearest_index(array, value)
|
||||
return array[idx]
|
||||
|
||||
|
||||
def find_nearest_index(array: np.ndarray, value: Number) -> int:
|
||||
"""
|
||||
Find the nearest index for a value in array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
array : np.ndarray
|
||||
array we are searching for a value in
|
||||
value : element of the array
|
||||
value to search for
|
||||
|
||||
Returns
|
||||
--------
|
||||
found_index : int
|
||||
the index at which the numerically closest element to `value`
|
||||
was found at
|
||||
|
||||
References
|
||||
----------
|
||||
taken from http://stackoverflow.com/questions/2566412/find-nearest-value-in-numpy-array
|
||||
|
||||
"""
|
||||
return (np.abs(array-value)).argmin()
|
||||
|
||||
|
||||
def slice_domain(x: np.ndarray, domain: tuple):
|
||||
"""
|
||||
Returns a slice object closest to the `domain` of `x`
|
||||
|
||||
domain = x[slice_domain(x, (start, stop))]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
vector : np.ndarray
|
||||
an array of values
|
||||
domain : tuple
|
||||
tuple of (start,stop) values defining the domain over
|
||||
which to slice
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> x = linspace(0,10,101)
|
||||
>>> idx = slice_domain(x, (2,6))
|
||||
>>> x[idx]
|
||||
|
||||
"""
|
||||
start = find_nearest_index(x, domain[0])
|
||||
stop = find_nearest_index(x, domain[1])
|
||||
return slice(start, stop+1)
|
||||
|
||||
# file IO
|
||||
|
||||
|
||||
def get_fid(file, *args, **kwargs):
|
||||
r"""
|
||||
Return a file object, given a filename or file object.
|
||||
|
||||
Useful when you want to allow the arguments of a function to
|
||||
be either files or filenames
|
||||
|
||||
Parameters
|
||||
----------
|
||||
file : str/unicode, Path, or file-object
|
||||
file to open
|
||||
\*args, \*\*kwargs : arguments and keyword arguments to `open()`
|
||||
|
||||
Returns
|
||||
-------
|
||||
fid : file object
|
||||
|
||||
"""
|
||||
if isinstance(file, (str, Path)):
|
||||
return open(file, *args, **kwargs)
|
||||
else:
|
||||
return file
|
||||
|
||||
|
||||
def get_extn(filename: str | Path) -> str:
|
||||
"""
|
||||
Get the extension from a filename.
|
||||
|
||||
The extension is defined as everything passed the last '.'.
|
||||
Returns None if it ain't got one
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : string or Path
|
||||
the filename
|
||||
|
||||
Returns
|
||||
-------
|
||||
ext : string, None
|
||||
either the extension (not including '.') or None if there
|
||||
isn't one
|
||||
|
||||
"""
|
||||
|
||||
if isinstance(filename, Path):
|
||||
return filename.suffix.strip('.') or None
|
||||
|
||||
ext = os.path.splitext(filename)[-1]
|
||||
if len(ext) == 0:
|
||||
return None
|
||||
else:
|
||||
return ext[1:]
|
||||
|
||||
|
||||
def basename_noext(filename: str) -> str:
|
||||
"""
|
||||
Get the basename and strips extension.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : string
|
||||
the filename
|
||||
|
||||
Returns
|
||||
-------
|
||||
basename : str
|
||||
file basename (ie. without extension)
|
||||
|
||||
"""
|
||||
return os.path.splitext(os.path.basename(filename))[0]
|
||||
|
||||
|
||||
# git
|
||||
def git_version(modname: str) -> str:
|
||||
"""
|
||||
Return output 'git describe', executed in a module's root directory.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
modname : str
|
||||
module name
|
||||
|
||||
Returns
|
||||
-------
|
||||
out : str
|
||||
output of 'git describe'
|
||||
|
||||
"""
|
||||
mod = __import__(modname)
|
||||
mod_dir = os.path.split(mod.__file__)[0]
|
||||
p = Popen(['git', 'describe'], stdout=PIPE, stderr=PIPE, cwd=mod_dir)
|
||||
|
||||
try:
|
||||
out, er = p.communicate()
|
||||
except(OSError):
|
||||
return None
|
||||
|
||||
out = out.strip('\n')
|
||||
if out == '':
|
||||
return None
|
||||
return out
|
||||
|
||||
|
||||
def dict_2_recarray(d: dict, delim: str, dtype: list[tuple]) -> np.ndarray:
|
||||
"""
|
||||
Turn a dictionary of structured keys to a record array of objects.
|
||||
|
||||
This is useful if you save data-base like meta-data in the form
|
||||
or file-naming conventions, aka 'the poor-mans database'
|
||||
|
||||
Parameters
|
||||
----------
|
||||
d : dict
|
||||
dictionnary of structured keys
|
||||
delim : str
|
||||
delimiter string
|
||||
dtype : list of tuple
|
||||
list of type, where a type is tuple like ('type_name', type)
|
||||
|
||||
Returns
|
||||
-------
|
||||
ra : numpy.array
|
||||
|
||||
Examples
|
||||
--------
|
||||
Given a directory of networks like:
|
||||
|
||||
>>> ls
|
||||
a1,0.0,0.0.s1p a1,3.0,3.0.s1p a2,3.0,-3.0.s1p b1,-3.0,3.0.s1p
|
||||
...
|
||||
|
||||
you can sort based on the values or each field, after defining their
|
||||
type with `dtype`. The `values` field accesses the objects.
|
||||
|
||||
>>> d = rf.read_all_networks('/tmp/')
|
||||
>>> delim = ','
|
||||
>>> dtype = [('name', object), ('voltage', float), ('current', float)]
|
||||
>>> ra = dict_2_recarray(d=rf.ran(dir), delim=delim, dtype =dtype)
|
||||
|
||||
then you can sift like you do with numpy arrays
|
||||
|
||||
>>> ra[ra['voltage'] < 3]['values']
|
||||
array([1-Port Network: 'a2,0.0,-3.0', 450-800 GHz, 101 pts, z0=[ 50.+0.j],
|
||||
1-Port Network: 'b1,0.0,3.0', 450-800 GHz, 101 pts, z0=[ 50.+0.j],
|
||||
1-Port Network: 'a1,0.0,-3.0', 450-800 GHz, 101 pts, z0=[ 50.+0.j],
|
||||
"""
|
||||
|
||||
split_keys = [tuple(k.split(delim)+[d[k]]) for k in d.keys()]
|
||||
x = np.array(split_keys, dtype=dtype+[('values',object)])
|
||||
return x
|
||||
|
||||
|
||||
def findReplace(directory: str, find: str, replace: str, file_pattern: str):
|
||||
r"""
|
||||
Find/replace some txt in all files in a directory, recursively.
|
||||
|
||||
This was found in [1]_ .
|
||||
|
||||
Parameters
|
||||
----------
|
||||
directory : str
|
||||
path of a directory
|
||||
find : str
|
||||
pattern to search for
|
||||
replace : str
|
||||
string to replace with
|
||||
file_pattern : str
|
||||
file pattern for filtering. Ex: '\*.txt'.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> rf.findReplace('some_dir', 'find this', 'replace with this', '*.txt')
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] http://stackoverflow.com/questions/4205854/python-way-to-recursively-find-and-replace-string-in-text-files
|
||||
"""
|
||||
for path, _dirs, files in os.walk(os.path.abspath(directory)):
|
||||
for filename in fnmatch.filter(files, file_pattern):
|
||||
filepath = os.path.join(path, filename)
|
||||
with open(filepath) as f:
|
||||
s = f.read()
|
||||
s = s.replace(find, replace)
|
||||
with open(filepath, "w") as f:
|
||||
f.write(s)
|
||||
|
||||
|
||||
# general purpose objects
|
||||
|
||||
class HomoList(collections.abc.Sequence):
|
||||
"""
|
||||
A Homogeneous Sequence.
|
||||
|
||||
Provides a class for a list-like object which contains
|
||||
homogeneous values. Attributes of the values can be accessed through
|
||||
the attributes of HomoList. Searching is done like numpy arrays.
|
||||
|
||||
Initialized from a list of all the same type
|
||||
|
||||
>>> h = HomoDict([Foo(...), Foo(...)])
|
||||
|
||||
The individual values of `h` can be access in identical fashion to
|
||||
Lists.
|
||||
|
||||
>>> h[0]
|
||||
|
||||
Assuming that `Foo` has property `prop` and function `func` ...
|
||||
|
||||
Access elements' properties:
|
||||
|
||||
>>> h.prop
|
||||
|
||||
Access elements' functions:
|
||||
|
||||
>>> h.func()
|
||||
|
||||
Searching:
|
||||
|
||||
>>> h[h.prop == value]
|
||||
>>> h[h.prop < value]
|
||||
|
||||
Multiple search:
|
||||
|
||||
>>> h[set(h.prop==value1) & set( h.prop2==value2)]
|
||||
|
||||
Combos:
|
||||
|
||||
>>> h[h.prop==value].func()
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, list_):
|
||||
self.store = list(list_)
|
||||
|
||||
def __eq__(self, value):
|
||||
return [k for k in range(len(self)) if self.store[k] == value ]
|
||||
|
||||
def __ne__(self, value):
|
||||
return [k for k in range(len(self)) if self.store[k] != value ]
|
||||
|
||||
def __gt__(self, value):
|
||||
return [k for k in range(len(self)) if self.store[k] > value ]
|
||||
|
||||
def __ge__(self, value):
|
||||
return [k for k in range(len(self)) if self.store[k] >= value ]
|
||||
|
||||
def __lt__(self, value):
|
||||
return [k for k in range(len(self)) if self.store[k] < value ]
|
||||
|
||||
def __le__(self, value):
|
||||
return [k for k in range(len(self)) if self.store[k] <= value ]
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.__class__(
|
||||
[k.__getattribute__(name) for k in self.store])
|
||||
|
||||
def __getitem__(self, idx):
|
||||
try:
|
||||
return self.store[idx]
|
||||
except(TypeError):
|
||||
return self.__class__([self.store[k] for k in idx])
|
||||
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.__class__(
|
||||
[k(*args,**kwargs) for k in self.store])
|
||||
|
||||
def __setitem__(self, idx, value):
|
||||
self.store[idx] = value
|
||||
|
||||
def __delitem__(self, idx):
|
||||
del self.store[idx]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.store)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.store)
|
||||
|
||||
def __str__(self):
|
||||
return pprint.pformat(self.store)
|
||||
|
||||
def __repr__(self):
|
||||
return pprint.pformat(self.store)
|
||||
|
||||
|
||||
class HomoDict(collections.abc.MutableMapping):
|
||||
"""
|
||||
A Homogeneous Mutable Mapping.
|
||||
|
||||
Provides a class for a dictionary-like object which contains
|
||||
homogeneous values. Attributes of the values can be accessed through
|
||||
the attributes of HomoDict. Searching is done like numpy arrays.
|
||||
|
||||
Initialized from a dictionary containing values of all the same type
|
||||
|
||||
>>> h = HomoDict({'a':Foo(...),'b': Foo(...), 'c':Foo(..)})
|
||||
|
||||
The individual values of `h` can be access in identical fashion to
|
||||
Dictionaries.
|
||||
|
||||
>>> h['key']
|
||||
|
||||
Assuming that `Foo` has property `prop` and function `func` ...
|
||||
|
||||
Access elements' properties:
|
||||
|
||||
>>> h.prop
|
||||
|
||||
Access elements' functions:
|
||||
|
||||
>>> h.func()
|
||||
|
||||
Searching:
|
||||
|
||||
>>> h[h.prop == value]
|
||||
>>> h[h.prop < value]
|
||||
|
||||
Multiple search:
|
||||
|
||||
>>> h[set(h.prop==value1) & set( h.prop2==value2)]
|
||||
|
||||
Combos:
|
||||
|
||||
>>> h[h.prop==value].func()
|
||||
"""
|
||||
def __init__(self, dict_):
|
||||
self.store = dict(dict_)
|
||||
|
||||
def __eq__(self, value):
|
||||
return [k for k in self.store if self.store[k] == value ]
|
||||
|
||||
def __ne__(self, value):
|
||||
return [k for k in self.store if self.store[k] != value ]
|
||||
|
||||
def __gt__(self, value):
|
||||
return [k for k in self.store if self.store[k] > value ]
|
||||
|
||||
def __ge__(self, value):
|
||||
return [k for k in self.store if self.store[k] >= value ]
|
||||
|
||||
def __lt__(self, value):
|
||||
return [k for k in self.store if self.store[k] < value ]
|
||||
|
||||
def __le__(self, value):
|
||||
return [k for k in self.store if self.store[k] <= value ]
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.__class__(
|
||||
{k: getattr(self.store[k],name) for k in self.store})
|
||||
|
||||
def __getitem__(self, key):
|
||||
if isinstance(key, str):
|
||||
return self.store[key]
|
||||
else:
|
||||
c = self.__class__({k:self.store[k] for k in key})
|
||||
return c
|
||||
#if len(c) == 1:
|
||||
# return c.store.values()[0]
|
||||
#else:
|
||||
# return c
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.__class__(
|
||||
{k: self.store[k](*args, **kwargs) for k in self.store})
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.store[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.store[key]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.store)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.store)
|
||||
|
||||
def __str__(self):
|
||||
return pprint.pformat(self.store)
|
||||
|
||||
def __repr__(self):
|
||||
return pprint.pformat(self.store)
|
||||
|
||||
|
||||
def copy(self):
|
||||
return HomoDict(self.store)
|
||||
|
||||
|
||||
def filter_nones(self):
|
||||
self.store = {k:self.store[k] for k in self.store \
|
||||
if self.store[k] is not None}
|
||||
|
||||
def filter(self, **kwargs):
|
||||
"""
|
||||
Filter self based on kwargs
|
||||
|
||||
This is equivalent to:
|
||||
|
||||
>>> h = HomoDict(...)
|
||||
>>> for k in kwargs:
|
||||
>>> h = h[k ==kwargs[k]]
|
||||
>>> return h
|
||||
|
||||
prefixing the kwarg value with a '!' causes a not equal test (!=)
|
||||
|
||||
Examples
|
||||
----------
|
||||
>>> h = HomoDict(...)
|
||||
>>> h.filter(name='jean', age = '18', gender ='!female')
|
||||
|
||||
"""
|
||||
a = self
|
||||
for k in kwargs:
|
||||
if kwargs[k][0] == '!':
|
||||
a = a[a.__getattr__(k) != kwargs[k][1:]]
|
||||
else:
|
||||
a = a[a.__getattr__(k) == kwargs[k]]
|
||||
return a
|
||||
|
||||
|
||||
def has_duplicate_value(value: Any, values: Iterable, index: int) -> bool | int:
|
||||
"""
|
||||
Check if there is another value of the current index in the list.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
value : Any
|
||||
any value in a list
|
||||
values : Iterable
|
||||
the iterable containing the values
|
||||
index : int
|
||||
the index of the current item we are checking for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
index : bool or int
|
||||
returns None if no duplicate found, or the index of the first found duplicate
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> rf.has_duplicate_value(0, [1, 2, 0, 3, 0], -1) # -> 2
|
||||
>>> rf.has_duplicate_value(0, [1, 2, 0, 3, 0], 2) # -> 4
|
||||
>>> rf.has_duplicate_value(3, [1, 2, 0, 3, 0], 0) # -> 3
|
||||
>>> rf.has_duplicate_value(3, [1, 2, 0, 3, 0], 3) # -> False
|
||||
"""
|
||||
|
||||
for i, val in enumerate(values):
|
||||
if i == index:
|
||||
continue
|
||||
if value == val:
|
||||
return i
|
||||
return False
|
||||
|
||||
|
||||
def unique_name(name: str, names: list, exclude: int = -1) -> str:
|
||||
"""
|
||||
Pass in a name and a list of names, and increment with _## as necessary to ensure a unique name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
the chosen name, to be modified if necessary
|
||||
names : list
|
||||
list of names (str)
|
||||
exclude : int, optional
|
||||
the index of an item to be excluded from the search. Default is -1.
|
||||
|
||||
Returns
|
||||
-------
|
||||
unique_name : str
|
||||
|
||||
"""
|
||||
if not has_duplicate_value(name, names, exclude):
|
||||
return name
|
||||
else:
|
||||
if re.match(r"_\d\d", name[-3:]):
|
||||
name_base = name[:-3]
|
||||
suffix = int(name[-2:])
|
||||
else:
|
||||
name_base = name
|
||||
suffix = 1
|
||||
|
||||
for num in range(suffix, 100, 1):
|
||||
name = f"{name_base:s}_{num:02d}"
|
||||
if not has_duplicate_value(name, names, exclude):
|
||||
break
|
||||
return name
|
||||
|
||||
|
||||
def smooth(x: np.ndarray, window_len: int = 11, window: str = 'flat') -> np.ndarray:
|
||||
"""
|
||||
Smooth the data using a window with requested size.
|
||||
|
||||
Based on the function from the scipy cookbook [#]_
|
||||
|
||||
This method is based on the convolution of a scaled window with the signal.
|
||||
The signal is prepared by introducing reflected copies of the signal
|
||||
(with the window size) in both ends so that transient parts are minimized
|
||||
in the beginning and end part of the output signal.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : numpy.array
|
||||
the input signal
|
||||
window_len : int, optional
|
||||
the dimension of the smoothing window; should be an odd integer.
|
||||
Default is 11.
|
||||
window : str, optional
|
||||
the type of window from 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
|
||||
flat window will produce a moving average smoothing. Default is 'flat'
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : numpy.array
|
||||
The smoothed signal
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> t = linspace(-2, 2, 0.1)
|
||||
>>> x = sin(t) + randn(len(t))*0.1
|
||||
>>> y = smooth(x)
|
||||
|
||||
See Also
|
||||
--------
|
||||
numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, numpy.convolve
|
||||
scipy.signal.lfilter
|
||||
|
||||
Note
|
||||
----
|
||||
`length(output) != length(input)`.
|
||||
To correct this: `return y[(window_len/2-1):-(window_len/2)]` instead of just `y`.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [#] http://scipy-cookbook.readthedocs.io/items/SignalSmooth.html
|
||||
|
||||
"""
|
||||
|
||||
if x.ndim != 1:
|
||||
raise ValueError("smooth only accepts 1 dimension arrays.")
|
||||
|
||||
if x.size < window_len:
|
||||
raise ValueError("Input vector needs to be bigger than window size.")
|
||||
|
||||
if window_len < 3:
|
||||
return x
|
||||
|
||||
if window not in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']:
|
||||
raise ValueError("Window is one of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'")
|
||||
|
||||
s = np.r_[x[window_len - 1:0:-1], x, x[-2:-window_len - 1:-1]]
|
||||
if window == 'flat': # moving average
|
||||
w = np.ones(window_len, 'd')
|
||||
else:
|
||||
w = eval('np.' + window + '(window_len)')
|
||||
y = np.convolve(w / w.sum(), s, mode='same')
|
||||
return y[window_len-1:-(window_len-1)]
|
||||
|
||||
|
||||
class ProgressBar:
|
||||
"""
|
||||
A progress bar based off of the notebook/ipython progress bar from PyMC.
|
||||
|
||||
Useful when waiting for long operations such as taking a large number
|
||||
of VNA measurements that may take a few minutes.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from time import sleep
|
||||
>>> pb = rf.ProgressBar(10)
|
||||
>>> for idx in range(10):
|
||||
>>> sleep(1)
|
||||
>>> pb.animate(idx)
|
||||
|
||||
"""
|
||||
def __init__(self, iterations: int, label: str = "iterations"):
|
||||
"""
|
||||
Progress bar constructor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
iterations : int
|
||||
Number of expected iterations
|
||||
label : str, optional
|
||||
Progress bar label, by default "iterations"
|
||||
"""
|
||||
self.iterations = iterations
|
||||
self.label = label
|
||||
self.prog_bar = '[]'
|
||||
self.fill_char = '*'
|
||||
self.width = 50
|
||||
self.__update_amount(0)
|
||||
|
||||
def animate(self, iteration: int):
|
||||
"""
|
||||
Animate the progress bar.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
iteration : int
|
||||
current iteration
|
||||
"""
|
||||
print('\r', self, end='')
|
||||
sys.stdout.flush()
|
||||
self.update_iteration(iteration + 1)
|
||||
|
||||
def update_iteration(self, elapsed_iter: int):
|
||||
self.__update_amount((elapsed_iter / float(self.iterations)) * 100.0)
|
||||
self.prog_bar += ' %d of %s %s complete' % (elapsed_iter, self.iterations, self.label)
|
||||
|
||||
def __update_amount(self, new_amount: int):
|
||||
percent_done = int(round((new_amount / 100.0) * 100.0))
|
||||
all_full = self.width - 2
|
||||
num_hashes = int(round((percent_done / 100.0) * all_full))
|
||||
self.prog_bar = '[' + self.fill_char * num_hashes + ' ' * (all_full - num_hashes) + ']'
|
||||
pct_place = (len(self.prog_bar) // 2) - len(str(percent_done))
|
||||
pct_string = '%d%%' % percent_done
|
||||
self.prog_bar = self.prog_bar[0:pct_place] + \
|
||||
(pct_string + self.prog_bar[pct_place + len(pct_string):])
|
||||
|
||||
def __str__(self):
|
||||
return str(self.prog_bar)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def suppress_numpy_warnings(**kw):
|
||||
olderr = np.seterr(**kw)
|
||||
yield
|
||||
np.seterr(**olderr)
|
||||
|
||||
|
||||
def suppress_warning_decorator(msg):
|
||||
def suppress_warnings_decorated(func):
|
||||
@wraps(func)
|
||||
def suppressed_func(*k, **kw):
|
||||
with warnings.catch_warnings():
|
||||
warnings.filterwarnings("ignore", message=f"{msg}.*")
|
||||
res = func(*k, **kw)
|
||||
return res
|
||||
return suppressed_func
|
||||
return suppress_warnings_decorated
|
||||
@@ -2,6 +2,18 @@ import numpy as np
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Dict, Tuple
|
||||
|
||||
def cond_row_inf(A, use_pinv=True):
|
||||
"""行条件数 κ∞(A) = ||A||∞ * ||A^{-1}||∞;矩形阵用广义逆。"""
|
||||
A = np.asarray(A)
|
||||
Ainv = np.linalg.pinv(A) if (use_pinv or A.shape[0] != A.shape[1]) else np.linalg.inv(A)
|
||||
return np.linalg.norm(A, ord=np.inf) * np.linalg.norm(Ainv, ord=np.inf)
|
||||
|
||||
def cond_col_one(A, use_pinv=True):
|
||||
"""列条件数 κ1(A) = ||A||1 * ||A^{-1}||1;矩形阵用广义逆。"""
|
||||
A = np.asarray(A)
|
||||
Ainv = np.linalg.pinv(A) if (use_pinv or A.shape[0] != A.shape[1]) else np.linalg.inv(A)
|
||||
return np.linalg.norm(A, ord=1) * np.linalg.norm(Ainv, ord=1)
|
||||
|
||||
def generate_starting_poles(n_pairs: int, beta_min: float, beta_max: float, alpha_scale: float = 0.01):
|
||||
"""
|
||||
仅生成复共轭对: p = -alpha + j beta, p*。
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user