feat: 添加测试代码
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,7 +7,6 @@ env/
|
|||||||
.venv/
|
.venv/
|
||||||
.vscode/
|
.vscode/
|
||||||
.cache/
|
.cache/
|
||||||
test/
|
|
||||||
outputs/
|
outputs/
|
||||||
ovf.egg-info/
|
ovf.egg-info/
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
7
test/gvf_plot_poles.py
Normal file
7
test/gvf_plot_poles.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from ovf.core.GVFManager import GVFManager
|
||||||
|
|
||||||
|
gvf = GVFManager.load("outputs/mtee_gvf.pkl")
|
||||||
|
# gvf = GVFManager()
|
||||||
|
# gvf.load_from_datasets("examples/data/capa.json",npoles_cplx=2,max_points=20)
|
||||||
|
# gvf.save("outputs/capa_gvf.pkl")
|
||||||
|
gvf.plot_poles("outputs/mtee_poles",degree=3,geometry_1="L1",geometry_2="L2")
|
||||||
97
test/gvf_worst_rms_error.py
Normal file
97
test/gvf_worst_rms_error.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
from ovf.core.GVFManager import GVFManager
|
||||||
|
import numpy as np
|
||||||
|
import logging
|
||||||
|
|
||||||
|
def get_logger():
|
||||||
|
logger = logging.getLogger("gvf_worst_rms_error")
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
if not logger.hasHandlers():
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
ch.setLevel(logging.INFO)
|
||||||
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
ch.setFormatter(formatter)
|
||||||
|
logger.addHandler(ch)
|
||||||
|
return logger
|
||||||
|
|
||||||
|
logger = get_logger()
|
||||||
|
|
||||||
|
gvf = GVFManager()
|
||||||
|
gvf.load_from_datasets("examples/data/mlin.json",npoles_cplx=2,max_points=20,parameter_type="s",min_freqs=10e9,max_freqs=200e9)
|
||||||
|
gvf.save("outputs/mlin_gvf_2pole_s.pkl")
|
||||||
|
|
||||||
|
# gvf = GVFManager.load("outputs/mlin_gvf_2pole_s.pkl")
|
||||||
|
|
||||||
|
max_rms_error_info = {
|
||||||
|
"max_rms_error": 0.0,
|
||||||
|
"max_rms_error_l1": 0.0,
|
||||||
|
"max_rms_error_lmax": 0.0,
|
||||||
|
"max_rms_error_geom": None,
|
||||||
|
"max_rms_index": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
max_rms_error_l1_info = {
|
||||||
|
"max_rms_error": 0.0,
|
||||||
|
"max_rms_error_l1": 0.0,
|
||||||
|
"max_rms_error_lmax": 0.0,
|
||||||
|
"max_rms_error_geom": None,
|
||||||
|
"max_rms_index": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
max_rms_error_lmax_info = {
|
||||||
|
"max_rms_error": 0.0,
|
||||||
|
"max_rms_error_l1": 0.0,
|
||||||
|
"max_rms_error_lmax": 0.0,
|
||||||
|
"max_rms_error_geom": None,
|
||||||
|
"max_rms_index": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for ds in gvf.datasets:
|
||||||
|
rmse = ds.vf_manager.eigenval_rms_error[-1]
|
||||||
|
rmse_l1 = ds.vf_manager.eigenval_rms_error_l1[-1]
|
||||||
|
rmse_lmax = ds.vf_manager.eigenval_rms_error_lmax[-1]
|
||||||
|
# logger.info(f"Dataset: {ds.geometries}, RMS Error: {rmse}, RMS Error L1: {rmse_l1}, RMS Error Lmax: {rmse_lmax}")
|
||||||
|
logger.info(
|
||||||
|
f'W={ds.geometries["W"]},L={ds.geometries["L"]},rms={rmse},rms_l1={rmse_l1},rms_lmax={rmse_lmax},id={ds.id}',
|
||||||
|
)
|
||||||
|
|
||||||
|
if rmse > max_rms_error_info["max_rms_error"]:
|
||||||
|
max_rms_error_info["max_rms_error"] = rmse
|
||||||
|
max_rms_error_info["max_rms_error_l1"] = rmse_l1
|
||||||
|
max_rms_error_info["max_rms_error_lmax"] = rmse_lmax
|
||||||
|
max_rms_error_info["max_rms_error_geom"] = ds.geometries
|
||||||
|
max_rms_error_info["max_rms_index"] = ds.id
|
||||||
|
|
||||||
|
if rmse_l1 > max_rms_error_l1_info["max_rms_error_l1"]:
|
||||||
|
max_rms_error_l1_info["max_rms_error"] = rmse
|
||||||
|
max_rms_error_l1_info["max_rms_error_l1"] = rmse_l1
|
||||||
|
max_rms_error_l1_info["max_rms_error_lmax"] = rmse_lmax
|
||||||
|
max_rms_error_l1_info["max_rms_error_geom"] = ds.geometries
|
||||||
|
max_rms_error_l1_info["max_rms_index"] = ds.id
|
||||||
|
|
||||||
|
if rmse_lmax > max_rms_error_lmax_info["max_rms_error_lmax"]:
|
||||||
|
max_rms_error_lmax_info["max_rms_error"] = rmse
|
||||||
|
max_rms_error_lmax_info["max_rms_error_l1"] = rmse_l1
|
||||||
|
max_rms_error_lmax_info["max_rms_error_lmax"] = rmse_lmax
|
||||||
|
max_rms_error_lmax_info["max_rms_error_geom"] = ds.geometries
|
||||||
|
max_rms_error_lmax_info["max_rms_index"] = ds.id
|
||||||
|
|
||||||
|
print(f"Maximum RMS error across datasets: {max_rms_error_info}")
|
||||||
|
print(f"Geometry with maximum RMS error: {max_rms_error_info['max_rms_error_geom']}")
|
||||||
|
print(f"Index of dataset with maximum RMS error: {max_rms_error_info['max_rms_index']}")
|
||||||
|
|
||||||
|
print(f"Maximum RMS L1 error across datasets: {max_rms_error_l1_info}")
|
||||||
|
print(f"Geometry with maximum RMS L1 error: {max_rms_error_l1_info['max_rms_error_geom']}")
|
||||||
|
print(f"Index of dataset with maximum RMS L1 error: {max_rms_error_l1_info['max_rms_index']}")
|
||||||
|
|
||||||
|
print(f"Maximum RMS Lmax error across datasets: {max_rms_error_lmax_info}")
|
||||||
|
print(f"Geometry with maximum RMS Lmax error: {max_rms_error_lmax_info['max_rms_error_geom']}")
|
||||||
|
print(f"Index of dataset with maximum RMS Lmax error: {max_rms_error_lmax_info['max_rms_index']}")
|
||||||
|
|
||||||
|
gvf.plot_vf_responses_with_index("outputs/mlin_gvf_2pole_s_responses",int(max_rms_error_info['max_rms_index']))
|
||||||
|
gvf.plot_vf_responses_with_index("outputs/mlin_gvf_2pole_s_responses_l1",int(max_rms_error_l1_info['max_rms_index']))
|
||||||
|
gvf.plot_vf_responses_with_index("outputs/mlin_gvf_2pole_s_responses_lmax",int(max_rms_error_lmax_info['max_rms_index']))
|
||||||
|
|
||||||
|
gvf.plot_poles_in_2d("outputs/mlin_gvf_2pole_s_poles.html")
|
||||||
|
gvf.plot_vf_responses_with_index("outputs/mlin_gvf_2pole_s_responses",1399)
|
||||||
|
|
||||||
203
test/hungarian_match.py
Normal file
203
test/hungarian_match.py
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import numpy as np
|
||||||
|
from scipy.optimize import linear_sum_assignment
|
||||||
|
|
||||||
|
def pairwise_mac(Phi: np.ndarray, Psi: np.ndarray) -> np.ndarray:
|
||||||
|
"""
|
||||||
|
Pairwise MAC between mode shape sets Phi (d x m) and Psi (d x n).
|
||||||
|
Columns are mode shapes. Works for real or complex.
|
||||||
|
Returns an (m x n) array of MAC values in [0,1].
|
||||||
|
"""
|
||||||
|
# Normalize columns
|
||||||
|
Phi_n = Phi / np.linalg.norm(Phi, axis=0, keepdims=True)
|
||||||
|
Psi_n = Psi / np.linalg.norm(Psi, axis=0, keepdims=True)
|
||||||
|
G = Phi_n.conj().T @ Psi_n
|
||||||
|
return np.abs(G) ** 2
|
||||||
|
|
||||||
|
def eig_to_freq_damp_from_s(lam: np.ndarray):
|
||||||
|
"""
|
||||||
|
Continuous-time eigenvalues s = sigma + j*omega -> (f, zeta).
|
||||||
|
f in Hz, zeta in [0,1].
|
||||||
|
"""
|
||||||
|
sigma = lam.real
|
||||||
|
omega = np.abs(lam.imag)
|
||||||
|
f = omega / (2 * np.pi)
|
||||||
|
zeta = -sigma / np.sqrt(sigma**2 + omega**2 + 1e-18)
|
||||||
|
return f, zeta
|
||||||
|
|
||||||
|
def z_to_s(z: np.ndarray, dt: float) -> np.ndarray:
|
||||||
|
""" Map discrete-time eigenvalues z to continuous-time s via log(z)/dt. """
|
||||||
|
return np.log(z) / dt
|
||||||
|
|
||||||
|
def build_cost_matrix(
|
||||||
|
f1, z1, Phi1, f2, z2, Phi2,
|
||||||
|
w_f=1.0, w_z=0.5, w_mac=1.0, f_scale=None,
|
||||||
|
gate_df=0.20, gate_z=0.05, gate_mac=0.7,
|
||||||
|
cost_unassigned=2.0, big_M=1e6
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Construct a padded square cost matrix for Hungarian assignment with gating and dummies.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- f1, z1: (m,) arrays for set A (reference)
|
||||||
|
- Phi1: (d, m) mode shapes for set A
|
||||||
|
- f2, z2: (n,) arrays for set B (to match)
|
||||||
|
- Phi2: (d, n) mode shapes for set B
|
||||||
|
- w_f, w_z, w_mac: weights
|
||||||
|
- f_scale: normalization for frequency difference (default: max of both sets or 1.0)
|
||||||
|
- gate_df: max allowed normalized freq diff (Hz normalized) for feasible match
|
||||||
|
- gate_z: max allowed damping diff for feasible match
|
||||||
|
- gate_mac: min required MAC for feasible match
|
||||||
|
- cost_unassigned: penalty to leave a mode unmatched (tune to your problem)
|
||||||
|
- big_M: prohibitive cost for infeasible pairs
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
C_pad: (L, L) padded square cost matrix
|
||||||
|
sizes: dict with m, n, L
|
||||||
|
"""
|
||||||
|
f1 = np.asarray(f1).reshape(-1)
|
||||||
|
z1 = np.asarray(z1).reshape(-1)
|
||||||
|
f2 = np.asarray(f2).reshape(-1)
|
||||||
|
z2 = np.asarray(z2).reshape(-1)
|
||||||
|
|
||||||
|
m = f1.size
|
||||||
|
n = f2.size
|
||||||
|
|
||||||
|
if f_scale is None:
|
||||||
|
f_scale = max(np.max(f1) if m else 1.0, np.max(f2) if n else 1.0, 1.0)
|
||||||
|
|
||||||
|
# Pairwise components
|
||||||
|
DF = np.abs(f1[:, None] - f2[None, :]) / f_scale # (m,n)
|
||||||
|
DZ = np.abs(z1[:, None] - z2[None, :]) # (m,n)
|
||||||
|
MAC = pairwise_mac(Phi1, Phi2) if (Phi1.size and Phi2.size) else np.zeros((m, n))
|
||||||
|
one_minus_MAC = 1.0 - MAC
|
||||||
|
|
||||||
|
# Base cost
|
||||||
|
C = w_f * DF + w_z * DZ + w_mac * one_minus_MAC
|
||||||
|
|
||||||
|
# Gating
|
||||||
|
infeasible = (DF > gate_df) | (DZ > gate_z) | (MAC < gate_mac)
|
||||||
|
C = np.where(infeasible, big_M, C)
|
||||||
|
|
||||||
|
# Pad to square with dummy rows/cols so that unmatched is allowed
|
||||||
|
L = max(m, n)
|
||||||
|
if L == 0:
|
||||||
|
return np.zeros((0, 0)), {"m": m, "n": n, "L": L}
|
||||||
|
|
||||||
|
C_pad = np.full((L, L), cost_unassigned, dtype=float)
|
||||||
|
|
||||||
|
# Place real costs into top-left block
|
||||||
|
if m and n:
|
||||||
|
C_pad[:m, :n] = C
|
||||||
|
|
||||||
|
# For the padded parts (dummies), keep cost_unassigned (meaning: matching to dummy costs that penalty)
|
||||||
|
|
||||||
|
return C_pad, {"m": m, "n": n, "L": L}
|
||||||
|
|
||||||
|
def hungarian_match(
|
||||||
|
f1, z1, Phi1, f2, z2, Phi2,
|
||||||
|
w_f=1.0, w_z=0.5, w_mac=1.0, f_scale=None,
|
||||||
|
gate_df=0.20, gate_z=0.05, gate_mac=0.7,
|
||||||
|
cost_unassigned=2.0, big_M=1e6,
|
||||||
|
accept_cost_threshold=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Run Hungarian assignment and return matches and unmatched lists.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
matches: list of (i, j, cost) with i in [0,m) and j in [0,n)
|
||||||
|
unmatched_A: list of indices in A not matched to any real B (or rejected by accept threshold)
|
||||||
|
unmatched_B: list of indices in B not matched to any real A (or rejected by accept threshold)
|
||||||
|
C_pad: the square padded cost matrix actually optimized
|
||||||
|
"""
|
||||||
|
C_pad, sizes = build_cost_matrix(
|
||||||
|
f1, z1, Phi1, f2, z2, Phi2,
|
||||||
|
w_f, w_z, w_mac, f_scale,
|
||||||
|
gate_df, gate_z, gate_mac,
|
||||||
|
cost_unassigned, big_M
|
||||||
|
)
|
||||||
|
m, n, L = sizes["m"], sizes["n"], sizes["L"]
|
||||||
|
if L == 0:
|
||||||
|
return [], [], [], C_pad
|
||||||
|
|
||||||
|
row_ind, col_ind = linear_sum_assignment(C_pad)
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
unmatched_A = []
|
||||||
|
unmatched_B = []
|
||||||
|
|
||||||
|
# Interpret the assignment on the unpadded indices
|
||||||
|
for r, c in zip(row_ind, col_ind):
|
||||||
|
# Case 1: real-to-real
|
||||||
|
if r < m and c < n:
|
||||||
|
cost = C_pad[r, c]
|
||||||
|
if accept_cost_threshold is not None and cost > accept_cost_threshold:
|
||||||
|
unmatched_A.append(r)
|
||||||
|
unmatched_B.append(c)
|
||||||
|
elif cost >= big_M:
|
||||||
|
# Infeasible pair forced by padding; treat as unmatched
|
||||||
|
unmatched_A.append(r)
|
||||||
|
unmatched_B.append(c)
|
||||||
|
else:
|
||||||
|
matches.append((r, c, float(cost)))
|
||||||
|
# Case 2: A matched to a dummy column => A unmatched
|
||||||
|
elif r < m and c >= n:
|
||||||
|
unmatched_A.append(r)
|
||||||
|
# Case 3: dummy row matched to real B => B unmatched
|
||||||
|
elif r >= m and c < n:
|
||||||
|
unmatched_B.append(c)
|
||||||
|
# Case 4: dummy-dummy (should not affect)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Deduplicate unmatched indices in case of multiple reasons
|
||||||
|
unmatched_A = sorted(set([i for i in unmatched_A if 0 <= i < m]))
|
||||||
|
unmatched_B = sorted(set([j for j in unmatched_B if 0 <= j < n]))
|
||||||
|
|
||||||
|
return matches, unmatched_A, unmatched_B, C_pad
|
||||||
|
|
||||||
|
def demo():
|
||||||
|
# Synthetic example: 3 reference modes vs 4 estimated modes
|
||||||
|
rng = np.random.default_rng(42)
|
||||||
|
d = 3 # DOF of mode shapes
|
||||||
|
|
||||||
|
# Reference modes (A)
|
||||||
|
fA = np.array([1.60, 3.00, 7.50]) # Hz
|
||||||
|
zA = np.array([0.010, 0.020, 0.005]) # damping ratios
|
||||||
|
PhiA = np.array([
|
||||||
|
[1.0, 0.1, 0.2],
|
||||||
|
[0.2, 1.0, 0.1],
|
||||||
|
[0.1, 0.2, 1.0],
|
||||||
|
], dtype=float)
|
||||||
|
PhiA = PhiA / np.linalg.norm(PhiA, axis=0, keepdims=True)
|
||||||
|
|
||||||
|
# Estimated modes (B): 3 near matches + 1 spurious
|
||||||
|
fB = np.array([1.62, 7.48, 2.95, 10.0])
|
||||||
|
zB = np.array([0.011, 0.006, 0.022, 0.010])
|
||||||
|
# Perturb PhiA columns to create similar shapes (plus one unrelated)
|
||||||
|
Psi_sim = PhiA.copy()
|
||||||
|
Psi_sim += 0.03 * rng.standard_normal(Psi_sim.shape)
|
||||||
|
Psi_sim = Psi_sim / np.linalg.norm(Psi_sim, axis=0, keepdims=True)
|
||||||
|
Psi_spur = rng.standard_normal((d, 1))
|
||||||
|
Psi_spur = Psi_spur / np.linalg.norm(Psi_spur, axis=0, keepdims=True)
|
||||||
|
PhiB = np.concatenate([Psi_sim[:, [0, 2, 1]], Psi_spur], axis=1) # reorder to make matching non-trivial
|
||||||
|
|
||||||
|
matches, unA, unB, C_pad = hungarian_match(
|
||||||
|
fA, zA, PhiA, fB, zB, PhiB,
|
||||||
|
w_f=1.0, w_z=0.5, w_mac=1.0,
|
||||||
|
f_scale=max(fA.max(), fB.max()),
|
||||||
|
gate_df=0.25, gate_z=0.05, gate_mac=0.6,
|
||||||
|
cost_unassigned=1.2, # tune this: lower => more likely to leave spurious unmatched
|
||||||
|
big_M=1e6,
|
||||||
|
accept_cost_threshold=0.9
|
||||||
|
)
|
||||||
|
|
||||||
|
print("Cost matrix (padded):\n", np.round(C_pad, 3))
|
||||||
|
print("\nMatches (A_i -> B_j, cost):")
|
||||||
|
for i, j, c in matches:
|
||||||
|
print(f" A[{i}] -> B[{j}], cost={c:.3f}, fA={fA[i]:.2f}, fB={fB[j]:.2f}, zA={zA[i]:.3f}, zB={zB[j]:.3f}")
|
||||||
|
|
||||||
|
print("\nUnmatched in A:", unA)
|
||||||
|
print("Unmatched in B:", unB)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
demo()
|
||||||
6
test/import_mlin_export_y.py
Normal file
6
test/import_mlin_export_y.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import skrf as rf
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
network = rf.Network("/tmp/paramer/simulation/8916/8916.s2p")
|
||||||
|
|
||||||
|
network.write_touchstone("outputs/mlin_8916.z2p",parameter="Z")
|
||||||
166
test/logger.py
Normal file
166
test/logger.py
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
# metrics_logger.py (with colorlog console)
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
# --- optional colorlog ---
|
||||||
|
try:
|
||||||
|
from colorlog import ColoredFormatter
|
||||||
|
HAVE_COLORLOG = True
|
||||||
|
except Exception:
|
||||||
|
HAVE_COLORLOG = False
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- JSON 格式化器 ----------
|
||||||
|
class JSONMetricsFormatter(logging.Formatter):
|
||||||
|
"""将记录格式化为一行 JSON(JSONL)"""
|
||||||
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
|
payload = {
|
||||||
|
"ts": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"level": record.levelname,
|
||||||
|
"event": getattr(record, "event", "metrics"),
|
||||||
|
"dataset": {"W": getattr(record, "W", None), "L": getattr(record, "L", None)},
|
||||||
|
"rms": getattr(record, "rms", None),
|
||||||
|
"rms_l1": getattr(record, "rms_l1", None),
|
||||||
|
"rms_lmax": getattr(record, "rms_lmax", None),
|
||||||
|
}
|
||||||
|
if hasattr(record, "step") and record.step is not None:
|
||||||
|
payload["step"] = record.step
|
||||||
|
if hasattr(record, "tag") and record.tag:
|
||||||
|
payload["tag"] = record.tag
|
||||||
|
if record.msg and record.msg not in ("", None):
|
||||||
|
payload["msg"] = record.getMessage()
|
||||||
|
return json.dumps(payload, ensure_ascii=False)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_human_message(
|
||||||
|
W: float, L: float, rms: float, rms_l1: float, rms_lmax: float,
|
||||||
|
step: int | None = None, tag: str | None = None
|
||||||
|
) -> str:
|
||||||
|
parts = [
|
||||||
|
f"Dataset[W={W}, L={L}]",
|
||||||
|
f"RMS={rms:.12e}",
|
||||||
|
f"L1={rms_l1:.12e}",
|
||||||
|
f"Lmax={rms_lmax:.12e}",
|
||||||
|
]
|
||||||
|
if step is not None:
|
||||||
|
parts.append(f"step={step}")
|
||||||
|
if tag:
|
||||||
|
parts.append(f"tag={tag}")
|
||||||
|
return " | ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- 日志器工厂 ----------
|
||||||
|
def get_metrics_logger(
|
||||||
|
name: str = "metrics",
|
||||||
|
log_dir: str = "./logs",
|
||||||
|
json_filename: str = "metrics.jsonl",
|
||||||
|
level: int = logging.INFO,
|
||||||
|
max_bytes: int = 10 * 1024 * 1024,
|
||||||
|
backup_count: int = 3,
|
||||||
|
use_colorlog: bool = True,
|
||||||
|
) -> logging.Logger:
|
||||||
|
"""
|
||||||
|
创建同时输出到控制台(可选彩色)和 JSONL 文件的日志器。
|
||||||
|
- 控制台:彩色(colorlog 存在且启用)或普通
|
||||||
|
- 文件:结构化 JSONL
|
||||||
|
"""
|
||||||
|
logger = logging.getLogger(name)
|
||||||
|
logger.setLevel(level)
|
||||||
|
logger.propagate = False
|
||||||
|
|
||||||
|
if not logger.handlers:
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# ---- 控制台 handler ----
|
||||||
|
ch = logging.StreamHandler()
|
||||||
|
ch.setLevel(level)
|
||||||
|
if HAVE_COLORLOG and use_colorlog:
|
||||||
|
ch.setFormatter(
|
||||||
|
ColoredFormatter(
|
||||||
|
fmt="%(asctime)s | %(log_color)s%(levelname)-5s%(reset)s | %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
log_colors={
|
||||||
|
"DEBUG": "cyan",
|
||||||
|
"INFO": "green",
|
||||||
|
"WARNING": "yellow",
|
||||||
|
"ERROR": "red",
|
||||||
|
"CRITICAL": "bold_red",
|
||||||
|
},
|
||||||
|
secondary_log_colors={}, # 需要给 message 局部再上色时可用
|
||||||
|
style="%",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ch.setFormatter(logging.Formatter(
|
||||||
|
fmt="%(asctime)s | %(levelname)-5s | %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
))
|
||||||
|
logger.addHandler(ch)
|
||||||
|
|
||||||
|
# ---- JSONL 文件 handler(带滚动)----
|
||||||
|
fh = RotatingFileHandler(
|
||||||
|
filename=os.path.join(log_dir, json_filename),
|
||||||
|
maxBytes=max_bytes,
|
||||||
|
backupCount=backup_count,
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
fh.setLevel(level)
|
||||||
|
fh.setFormatter(JSONMetricsFormatter())
|
||||||
|
logger.addHandler(fh)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- 便捷记录函数 ----------
|
||||||
|
def log_metrics(
|
||||||
|
logger: logging.Logger,
|
||||||
|
W: float,
|
||||||
|
L: float,
|
||||||
|
rms: float,
|
||||||
|
rms_l1: float,
|
||||||
|
rms_lmax: float,
|
||||||
|
*,
|
||||||
|
step: int | None = None,
|
||||||
|
tag: str | None = None,
|
||||||
|
msg: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
以结构化方式记录一次度量。
|
||||||
|
- 终端(可彩色)显示:人类可读的一行
|
||||||
|
- 文件:结构化 JSONL(携带全部字段)
|
||||||
|
"""
|
||||||
|
human_msg = msg or _build_human_message(W, L, rms, rms_l1, rms_lmax, step, tag)
|
||||||
|
extra = {
|
||||||
|
"event": "metrics",
|
||||||
|
"W": float(W),
|
||||||
|
"L": float(L),
|
||||||
|
"rms": float(rms),
|
||||||
|
"rms_l1": float(rms_l1),
|
||||||
|
"rms_lmax": float(rms_lmax),
|
||||||
|
"step": step,
|
||||||
|
"tag": tag,
|
||||||
|
}
|
||||||
|
logger.info(human_msg, extra=extra)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- 可选:解析旧日志行(你给的样例) ----------
|
||||||
|
LEGACY_LINE_RE = re.compile(
|
||||||
|
r"Dataset:\s*\{[^}]*['\"]?W['\"]?\s*:\s*([0-9.+\-eE]+)\s*,\s*['\"]?L['\"]?\s*:\s*([0-9.+\-eE]+)\s*\}\s*,\s*"
|
||||||
|
r"RMS Error:\s*([0-9.+\-eE]+)\s*,\s*RMS Error L1:\s*([0-9.+\-eE]+)\s*,\s*RMS Error Lmax:\s*([0-9.+\-eE]+)"
|
||||||
|
)
|
||||||
|
|
||||||
|
def parse_legacy_line(line: str):
|
||||||
|
m = LEGACY_LINE_RE.search(line)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
W, L, rms, rms_l1, rms_lmax = map(float, m.groups())
|
||||||
|
return {"W": W, "L": L, "rms": rms, "rms_l1": rms_l1, "rms_lmax": rms_lmax}
|
||||||
|
|
||||||
|
def ingest_legacy_line(logger: logging.Logger, line: str, **kwargs):
|
||||||
|
parsed = parse_legacy_line(line)
|
||||||
|
if parsed:
|
||||||
|
log_metrics(logger, **parsed, **kwargs)
|
||||||
5
test/sweep.py
Normal file
5
test/sweep.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from examples.capa import run_capa
|
||||||
|
from examples.mtee import run_mtee
|
||||||
|
|
||||||
|
# run_capa()
|
||||||
|
run_mtee()
|
||||||
48
test/test_save_and_reload_model.py
Normal file
48
test/test_save_and_reload_model.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import skrf as rf
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
from ovf.core.VFManager import VFManager
|
||||||
|
from ovf.core.sample import auto_select_multple_ports
|
||||||
|
from ovf.core.basis.MultiPortOrthonormalBasis import MultiPortOrthonormalBasis
|
||||||
|
|
||||||
|
id = 3000
|
||||||
|
network = rf.Network(f"/tmp/paramer/simulation/3000/3000.s2p")
|
||||||
|
ports = network.nports
|
||||||
|
K = 5
|
||||||
|
|
||||||
|
full_freqences = network.f
|
||||||
|
noised_sampled_points = network.y.reshape(-1,ports,ports)
|
||||||
|
sampled_points = network.y.reshape(-1,ports,ports)
|
||||||
|
|
||||||
|
H,freqs = auto_select_multple_ports(noised_sampled_points,full_freqences,max_points=20)
|
||||||
|
|
||||||
|
def run_capa():
|
||||||
|
vf = VFManager(npoles_cplx=2,freqs=freqs,H=H,model=MultiPortOrthonormalBasis,iterations=K,verbose=False)
|
||||||
|
vf.fit()
|
||||||
|
vf.plot_metrics(show=False,save_path=f"outputs/{id}")
|
||||||
|
model_responses = vf.get_model_responses(full_freqences)
|
||||||
|
vf.plot_model_responses(show=False,save_path=f"outputs/{id}")
|
||||||
|
# vf.export(f"outputs/{id}")
|
||||||
|
vf.write(f"outputs/{id}")
|
||||||
|
|
||||||
|
def load_model():
|
||||||
|
vf = VFManager.load(f"outputs/3000")
|
||||||
|
|
||||||
|
vf.plot_metrics(show=False,save_path=f"outputs/3001")
|
||||||
|
model_responses = vf.get_model_responses(full_freqences)
|
||||||
|
vf.plot_model_responses(show=False,save_path=f"outputs/3001")
|
||||||
|
# vf.export(f"outputs/{id}")
|
||||||
|
vf.write(f"outputs/3001")
|
||||||
|
|
||||||
|
def load_model1():
|
||||||
|
vf = VFManager.load(f"outputs/3001")
|
||||||
|
|
||||||
|
vf.plot_metrics(show=False,save_path=f"outputs/3002")
|
||||||
|
model_responses = vf.get_model_responses(full_freqences)
|
||||||
|
vf.plot_model_responses(show=False,save_path=f"outputs/3002")
|
||||||
|
# vf.export(f"outputs/{id}")
|
||||||
|
vf.write(f"outputs/3002")
|
||||||
|
|
||||||
|
run_capa()
|
||||||
|
load_model()
|
||||||
|
load_model1()
|
||||||
Reference in New Issue
Block a user