fix: enforce strict ruff lint (17 rule sets) across entire repo

- Expand ruff config from E/F to 17 rule sets (B, RUF, SIM, PIE, T20,
  ARG, ERA, A, BLE, RET, ISC, TCH, UP, C4, PERF)
- Fix 907 lint errors across all Python files (GUI, FPGA cosim,
  schematics scripts, simulations, utilities, tools)
- Replace all blind except-Exception with specific exception types
- Remove commented-out dead code (ERA001) from cosim/simulation files
- Modernize typing: deprecated typing.List/Dict/Tuple to builtins
- Fix unused args/loop vars, ambiguous unicode, perf anti-patterns
- Delete legacy GUI files V1-V4
- Add V7 test suite, requirements files
- All CI jobs pass: ruff (0 errors), py_compile, pytest (92/92),
  MCU tests (20/20), FPGA regression (25/25)
This commit is contained in:
Jason
2026-04-12 14:18:34 +05:45
parent b6e8eda130
commit 2106e24952
54 changed files with 4619 additions and 9063 deletions
+438
View File
@@ -0,0 +1,438 @@
#!/usr/bin/env python3
"""
AERIS-10 FMC Anti-Alias Filter — openEMS 3D EM Simulation
==========================================================
5th-order differential Butterworth LC LPF, fc ≈ 195 MHz
All components are 0402 (1.0 x 0.5 mm) on FR4 4-layer stackup.
Filter topology (each half of differential):
IN → R_series(49.9Ω) → L1(24nH) → C1(27pF)↓GND → L2(82nH) → C2(27pF)↓GND → L3(24nH) → OUT
Plus R_diff(100Ω) across input and output differential pairs.
PCB stackup:
L1: F.Cu (signal + components) — 35µm copper
Prepreg: 0.2104 mm
L2: In1.Cu (GND plane) — 35µm copper
Core: 1.0 mm
L3: In2.Cu (Power plane) — 35µm copper
Prepreg: 0.2104 mm
L4: B.Cu (signal) — 35µm copper
Total board thickness ≈ 1.6 mm
Differential trace: W=0.23mm, S=0.12mm gap → Zdiff≈100Ω
All 0402 pads: 0.5mm x 0.55mm with 0.5mm gap between pads
Simulation extracts 4-port S-parameters (differential in → differential out)
then converts to mixed-mode (Sdd11, Sdd21, Scc21) for analysis.
"""
import os
import sys
import numpy as np
sys.path.insert(0, '/Users/ganeshpanth/openEMS-Project/CSXCAD/python')
sys.path.insert(0, '/Users/ganeshpanth/openEMS-Project/openEMS/python')
os.environ['PATH'] = '/Users/ganeshpanth/opt/openEMS/bin:' + os.environ.get('PATH', '')
from CSXCAD import ContinuousStructure
from openEMS import openEMS
from openEMS.physical_constants import C0, EPS0
unit = 1e-3
f_start = 1e6
f_stop = 1e9
f_center = 150e6
f_IF_low = 120e6
f_IF_high = 180e6
max_res = C0 / f_stop / unit / 20
copper_t = 0.035
prepreg_t = 0.2104
core_t = 1.0
sub_er = 4.3
sub_tand = 0.02
cu_cond = 5.8e7
z_L4_bot = 0.0
z_L4_top = z_L4_bot + copper_t
z_pre2_top = z_L4_top + prepreg_t
z_L3_top = z_pre2_top + copper_t
z_core_top = z_L3_top + core_t
z_L2_top = z_core_top + copper_t
z_pre1_top = z_L2_top + prepreg_t
z_L1_bot = z_pre1_top
z_L1_top = z_L1_bot + copper_t
pad_w = 0.50
pad_l = 0.55
pad_gap = 0.50
comp_pitch = 1.5
trace_w = 0.23
trace_s = 0.12
pair_pitch = trace_w + trace_s
R_series = 49.9
R_diff_in = 100.0
R_diff_out = 100.0
L1_val = 24e-9
L2_val = 82e-9
L3_val = 24e-9
C1_val = 27e-12
C2_val = 27e-12
FDTD = openEMS(NrTS=50000, EndCriteria=1e-5)
FDTD.SetGaussExcite(0.5 * (f_start + f_stop), 0.5 * (f_stop - f_start))
FDTD.SetBoundaryCond(['PML_8'] * 6)
CSX = ContinuousStructure()
FDTD.SetCSX(CSX)
copper = CSX.AddMetal('copper')
gnd_metal = CSX.AddMetal('gnd_plane')
fr4_pre1 = CSX.AddMaterial(
'prepreg1', epsilon=sub_er, kappa=sub_tand * 2 * np.pi * f_center * EPS0 * sub_er
)
fr4_core = CSX.AddMaterial(
'core', epsilon=sub_er, kappa=sub_tand * 2 * np.pi * f_center * EPS0 * sub_er
)
fr4_pre2 = CSX.AddMaterial(
'prepreg2', epsilon=sub_er, kappa=sub_tand * 2 * np.pi * f_center * EPS0 * sub_er
)
y_P = +pair_pitch / 2
y_N = -pair_pitch / 2
x_port_in = -1.0
x_R_series = 0.0
x_L1 = x_R_series + comp_pitch
x_C1 = x_L1 + comp_pitch
x_L2 = x_C1 + comp_pitch
x_C2 = x_L2 + comp_pitch
x_L3 = x_C2 + comp_pitch
x_port_out = x_L3 + comp_pitch + 1.0
x_Rdiff_in = x_port_in - 0.5
x_Rdiff_out = x_port_out + 0.5
margin = 3.0
x_min = x_Rdiff_in - margin
x_max = x_Rdiff_out + margin
y_min = y_N - margin
y_max = y_P + margin
z_min = z_L4_bot - margin
z_max = z_L1_top + margin
fr4_pre1.AddBox([x_min, y_min, z_L2_top], [x_max, y_max, z_L1_bot], priority=1)
fr4_core.AddBox([x_min, y_min, z_L3_top], [x_max, y_max, z_core_top], priority=1)
fr4_pre2.AddBox([x_min, y_min, z_L4_top], [x_max, y_max, z_pre2_top], priority=1)
gnd_metal.AddBox(
[x_min + 0.5, y_min + 0.5, z_core_top], [x_max - 0.5, y_max - 0.5, z_L2_top], priority=10
)
def add_trace_segment(x_start, x_end, y_center, z_bot, z_top, w, metal, priority=20):
metal.AddBox(
[x_start, y_center - w / 2, z_bot], [x_end, y_center + w / 2, z_top], priority=priority
)
def add_0402_pads(x_center, y_center, z_bot, z_top, metal, priority=20):
x_left = x_center - pad_gap / 2 - pad_w / 2
metal.AddBox(
[x_left - pad_w / 2, y_center - pad_l / 2, z_bot],
[x_left + pad_w / 2, y_center + pad_l / 2, z_top],
priority=priority,
)
x_right = x_center + pad_gap / 2 + pad_w / 2
metal.AddBox(
[x_right - pad_w / 2, y_center - pad_l / 2, z_bot],
[x_right + pad_w / 2, y_center + pad_l / 2, z_top],
priority=priority,
)
return (x_left, x_right)
def add_lumped_element(
CSX, name, element_type, value, x_center, y_center, z_bot, z_top, direction='x'
):
x_left = x_center - pad_gap / 2 - pad_w / 2
x_right = x_center + pad_gap / 2 + pad_w / 2
if direction == 'x':
start = [x_left, y_center - pad_l / 4, z_bot]
stop = [x_right, y_center + pad_l / 4, z_top]
edir = 'x'
elif direction == 'y':
start = [x_center - pad_l / 4, y_center - pad_gap / 2 - pad_w / 2, z_bot]
stop = [x_center + pad_l / 4, y_center + pad_gap / 2 + pad_w / 2, z_top]
edir = 'y'
if element_type == 'R':
elem = CSX.AddLumpedElement(name, ny=edir, caps=True, R=value)
elif element_type == 'L':
elem = CSX.AddLumpedElement(name, ny=edir, caps=True, L=value)
elif element_type == 'C':
elem = CSX.AddLumpedElement(name, ny=edir, caps=True, C=value)
elem.AddBox(start, stop, priority=30)
return elem
def add_shunt_cap(
CSX, name, value, x_center, y_trace, _z_top_signal, _z_gnd_top, metal, priority=20,
):
metal.AddBox(
[x_center - pad_w / 2, y_trace - pad_l / 2, z_L1_bot],
[x_center + pad_w / 2, y_trace + pad_l / 2, z_L1_top],
priority=priority,
)
via_drill = 0.15
cap = CSX.AddLumpedElement(name, ny='z', caps=True, C=value)
cap.AddBox(
[x_center - via_drill, y_trace - via_drill, z_L2_top],
[x_center + via_drill, y_trace + via_drill, z_L1_bot],
priority=30,
)
via_metal = CSX.AddMetal(name + '_via')
via_metal.AddBox(
[x_center - via_drill, y_trace - via_drill, z_L2_top],
[x_center + via_drill, y_trace + via_drill, z_L1_bot],
priority=25,
)
add_trace_segment(
x_port_in, x_R_series - pad_gap / 2 - pad_w, y_P, z_L1_bot, z_L1_top, trace_w, copper
)
add_0402_pads(x_R_series, y_P, z_L1_bot, z_L1_top, copper)
add_lumped_element(CSX, 'R10', 'R', R_series, x_R_series, y_P, z_L1_bot, z_L1_top)
add_trace_segment(
x_R_series + pad_gap / 2 + pad_w,
x_L1 - pad_gap / 2 - pad_w,
y_P,
z_L1_bot,
z_L1_top,
trace_w,
copper,
)
add_0402_pads(x_L1, y_P, z_L1_bot, z_L1_top, copper)
add_lumped_element(CSX, 'L5', 'L', L1_val, x_L1, y_P, z_L1_bot, z_L1_top)
add_trace_segment(x_L1 + pad_gap / 2 + pad_w, x_C1, y_P, z_L1_bot, z_L1_top, trace_w, copper)
add_shunt_cap(CSX, 'C53', C1_val, x_C1, y_P, z_L1_top, z_L2_top, copper)
add_trace_segment(x_C1, x_L2 - pad_gap / 2 - pad_w, y_P, z_L1_bot, z_L1_top, trace_w, copper)
add_0402_pads(x_L2, y_P, z_L1_bot, z_L1_top, copper)
add_lumped_element(CSX, 'L8', 'L', L2_val, x_L2, y_P, z_L1_bot, z_L1_top)
add_trace_segment(x_L2 + pad_gap / 2 + pad_w, x_C2, y_P, z_L1_bot, z_L1_top, trace_w, copper)
add_shunt_cap(CSX, 'C55', C2_val, x_C2, y_P, z_L1_top, z_L2_top, copper)
add_trace_segment(x_C2, x_L3 - pad_gap / 2 - pad_w, y_P, z_L1_bot, z_L1_top, trace_w, copper)
add_0402_pads(x_L3, y_P, z_L1_bot, z_L1_top, copper)
add_lumped_element(CSX, 'L10', 'L', L3_val, x_L3, y_P, z_L1_bot, z_L1_top)
add_trace_segment(x_L3 + pad_gap / 2 + pad_w, x_port_out, y_P, z_L1_bot, z_L1_top, trace_w, copper)
add_trace_segment(
x_port_in, x_R_series - pad_gap / 2 - pad_w, y_N, z_L1_bot, z_L1_top, trace_w, copper
)
add_0402_pads(x_R_series, y_N, z_L1_bot, z_L1_top, copper)
add_lumped_element(CSX, 'R11', 'R', R_series, x_R_series, y_N, z_L1_bot, z_L1_top)
add_trace_segment(
x_R_series + pad_gap / 2 + pad_w,
x_L1 - pad_gap / 2 - pad_w,
y_N,
z_L1_bot,
z_L1_top,
trace_w,
copper,
)
add_0402_pads(x_L1, y_N, z_L1_bot, z_L1_top, copper)
add_lumped_element(CSX, 'L6', 'L', L1_val, x_L1, y_N, z_L1_bot, z_L1_top)
add_trace_segment(x_L1 + pad_gap / 2 + pad_w, x_C1, y_N, z_L1_bot, z_L1_top, trace_w, copper)
add_shunt_cap(CSX, 'C54', C1_val, x_C1, y_N, z_L1_top, z_L2_top, copper)
add_trace_segment(x_C1, x_L2 - pad_gap / 2 - pad_w, y_N, z_L1_bot, z_L1_top, trace_w, copper)
add_0402_pads(x_L2, y_N, z_L1_bot, z_L1_top, copper)
add_lumped_element(CSX, 'L7', 'L', L2_val, x_L2, y_N, z_L1_bot, z_L1_top)
add_trace_segment(x_L2 + pad_gap / 2 + pad_w, x_C2, y_N, z_L1_bot, z_L1_top, trace_w, copper)
add_shunt_cap(CSX, 'C56', C2_val, x_C2, y_N, z_L1_top, z_L2_top, copper)
add_trace_segment(x_C2, x_L3 - pad_gap / 2 - pad_w, y_N, z_L1_bot, z_L1_top, trace_w, copper)
add_0402_pads(x_L3, y_N, z_L1_bot, z_L1_top, copper)
add_lumped_element(CSX, 'L9', 'L', L3_val, x_L3, y_N, z_L1_bot, z_L1_top)
add_trace_segment(x_L3 + pad_gap / 2 + pad_w, x_port_out, y_N, z_L1_bot, z_L1_top, trace_w, copper)
R4_x = x_port_in - 0.3
copper.AddBox(
[R4_x - pad_l / 2, y_P - pad_w / 2, z_L1_bot],
[R4_x + pad_l / 2, y_P + pad_w / 2, z_L1_top],
priority=20,
)
copper.AddBox(
[R4_x - pad_l / 2, y_N - pad_w / 2, z_L1_bot],
[R4_x + pad_l / 2, y_N + pad_w / 2, z_L1_top],
priority=20,
)
R4_elem = CSX.AddLumpedElement('R4', ny='y', caps=True, R=R_diff_in)
R4_elem.AddBox([R4_x - pad_l / 4, y_N, z_L1_bot], [R4_x + pad_l / 4, y_P, z_L1_top], priority=30)
R18_x = x_port_out + 0.3
copper.AddBox(
[R18_x - pad_l / 2, y_P - pad_w / 2, z_L1_bot],
[R18_x + pad_l / 2, y_P + pad_w / 2, z_L1_top],
priority=20,
)
copper.AddBox(
[R18_x - pad_l / 2, y_N - pad_w / 2, z_L1_bot],
[R18_x + pad_l / 2, y_N + pad_w / 2, z_L1_top],
priority=20,
)
R18_elem = CSX.AddLumpedElement('R18', ny='y', caps=True, R=R_diff_out)
R18_elem.AddBox([R18_x - pad_l / 4, y_N, z_L1_bot], [R18_x + pad_l / 4, y_P, z_L1_top], priority=30)
port1 = FDTD.AddLumpedPort(
1,
50,
[x_port_in, y_P - trace_w / 2, z_L2_top],
[x_port_in, y_P + trace_w / 2, z_L1_bot],
'z',
excite=1.0,
)
port2 = FDTD.AddLumpedPort(
2,
50,
[x_port_in, y_N - trace_w / 2, z_L2_top],
[x_port_in, y_N + trace_w / 2, z_L1_bot],
'z',
excite=-1.0,
)
port3 = FDTD.AddLumpedPort(
3,
50,
[x_port_out, y_P - trace_w / 2, z_L2_top],
[x_port_out, y_P + trace_w / 2, z_L1_bot],
'z',
excite=0,
)
port4 = FDTD.AddLumpedPort(
4,
50,
[x_port_out, y_N - trace_w / 2, z_L2_top],
[x_port_out, y_N + trace_w / 2, z_L1_bot],
'z',
excite=0,
)
mesh = CSX.GetGrid()
mesh.SetDeltaUnit(unit)
mesh.AddLine('x', [x_min, x_max])
for x_comp in [x_R_series, x_L1, x_C1, x_L2, x_C2, x_L3]:
mesh.AddLine('x', np.linspace(x_comp - 1.0, x_comp + 1.0, 15))
mesh.AddLine('x', [x_port_in, x_port_out])
mesh.AddLine('x', [R4_x, R18_x])
mesh.AddLine('y', [y_min, y_max])
for y_trace in [y_P, y_N]:
mesh.AddLine('y', np.linspace(y_trace - 0.5, y_trace + 0.5, 10))
mesh.AddLine('z', [z_min, z_max])
mesh.AddLine('z', np.linspace(z_L4_bot - 0.1, z_L1_top + 0.1, 25))
mesh.SmoothMeshLines('x', max_res, ratio=1.4)
mesh.SmoothMeshLines('y', max_res, ratio=1.4)
mesh.SmoothMeshLines('z', max_res / 3, ratio=1.3)
sim_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'results')
if not os.path.exists(sim_path):
os.makedirs(sim_path)
CSX_file = os.path.join(sim_path, 'aaf_filter.xml')
CSX.Write2XML(CSX_file)
FDTD.Run(sim_path, cleanup=True, verbose=3)
freq = np.linspace(f_start, f_stop, 1001)
port1.CalcPort(sim_path, freq)
port2.CalcPort(sim_path, freq)
port3.CalcPort(sim_path, freq)
port4.CalcPort(sim_path, freq)
inc1 = port1.uf_inc
ref1 = port1.uf_ref
inc2 = port2.uf_inc
ref2 = port2.uf_ref
inc3 = port3.uf_inc
ref3 = port3.uf_ref
inc4 = port4.uf_inc
ref4 = port4.uf_ref
a_diff = (inc1 - inc2) / np.sqrt(2)
b_diff_in = (ref1 - ref2) / np.sqrt(2)
b_diff_out = (ref3 - ref4) / np.sqrt(2)
Sdd11 = b_diff_in / a_diff
Sdd21 = b_diff_out / a_diff
b_comm_out = (ref3 + ref4) / np.sqrt(2)
Scd21 = b_comm_out / a_diff
import matplotlib # noqa: E402
matplotlib.use('Agg')
import matplotlib.pyplot as plt # noqa: E402
fig, axes = plt.subplots(3, 1, figsize=(12, 14))
ax = axes[0]
Sdd21_dB = 20 * np.log10(np.abs(Sdd21) + 1e-15)
ax.plot(freq / 1e6, Sdd21_dB, 'b-', linewidth=2, label='|Sdd21| (Insertion Loss)')
ax.axvspan(
f_IF_low / 1e6, f_IF_high / 1e6, alpha=0.15, color='green', label='IF Band (120-180 MHz)'
)
ax.axhline(-3, color='r', linestyle='--', alpha=0.5, label='-3 dB')
ax.set_xlabel('Frequency (MHz)')
ax.set_ylabel('|Sdd21| (dB)')
ax.set_title('Anti-Alias Filter — Differential Insertion Loss')
ax.set_xlim([0, 1000])
ax.set_ylim([-60, 5])
ax.grid(True, alpha=0.3)
ax.legend()
ax = axes[1]
Sdd11_dB = 20 * np.log10(np.abs(Sdd11) + 1e-15)
ax.plot(freq / 1e6, Sdd11_dB, 'r-', linewidth=2, label='|Sdd11| (Return Loss)')
ax.axvspan(f_IF_low / 1e6, f_IF_high / 1e6, alpha=0.15, color='green', label='IF Band')
ax.axhline(-10, color='orange', linestyle='--', alpha=0.5, label='-10 dB')
ax.set_xlabel('Frequency (MHz)')
ax.set_ylabel('|Sdd11| (dB)')
ax.set_title('Anti-Alias Filter — Differential Return Loss')
ax.set_xlim([0, 1000])
ax.set_ylim([-40, 0])
ax.grid(True, alpha=0.3)
ax.legend()
ax = axes[2]
phase_Sdd21 = np.unwrap(np.angle(Sdd21))
group_delay = -np.diff(phase_Sdd21) / np.diff(2 * np.pi * freq) * 1e9
ax.plot(freq[1:] / 1e6, group_delay, 'g-', linewidth=2, label='Group Delay')
ax.axvspan(f_IF_low / 1e6, f_IF_high / 1e6, alpha=0.15, color='green', label='IF Band')
ax.set_xlabel('Frequency (MHz)')
ax.set_ylabel('Group Delay (ns)')
ax.set_title('Anti-Alias Filter — Group Delay')
ax.set_xlim([0, 500])
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plot_file = os.path.join(sim_path, 'aaf_filter_response.png')
plt.savefig(plot_file, dpi=150)
idx_120 = np.argmin(np.abs(freq - f_IF_low))
idx_150 = np.argmin(np.abs(freq - f_center))
idx_180 = np.argmin(np.abs(freq - f_IF_high))
idx_200 = np.argmin(np.abs(freq - 200e6))
idx_400 = np.argmin(np.abs(freq - 400e6))
csv_file = os.path.join(sim_path, 'aaf_sparams.csv')
np.savetxt(
csv_file,
np.column_stack([freq / 1e6, Sdd21_dB, Sdd11_dB, 20 * np.log10(np.abs(Scd21) + 1e-15)]),
header='Freq_MHz, Sdd21_dB, Sdd11_dB, Scd21_dB',
delimiter=',', fmt='%.6f'
)
+5 -8
View File
@@ -91,9 +91,9 @@ z_edges = np.concatenate([z_centers - slot_L/2.0, z_centers + slot_L/2.0])
# -------------------------
# Mesh lines — EXPLICIT (no GetLine calls)
# -------------------------
x_lines = sorted(set([x_min, -t_metal, 0.0, a, a+t_metal, x_max] + list(x_edges)))
x_lines = sorted({x_min, -t_metal, 0.0, a, a + t_metal, x_max, *list(x_edges)})
y_lines = [y_min, 0.0, b, b+t_metal, y_max]
z_lines = sorted(set([z_min, 0.0, L, z_max] + list(z_edges)))
z_lines = sorted({z_min, 0.0, L, z_max, *list(z_edges)})
mesh.AddLine('x', x_lines)
mesh.AddLine('y', y_lines)
@@ -123,7 +123,7 @@ pec.AddBox([-t_metal,-t_metal,0],[a+t_metal,0, L]) # bottom
pec.AddBox([-t_metal, b, 0], [a+t_metal,b+t_metal,L]) # top
# Slots = AIR boxes overriding the top metal
for zc, xc in zip(z_centers, x_centers):
for zc, xc in zip(z_centers, x_centers, strict=False):
x1, x2 = xc - slot_w/2.0, xc + slot_w/2.0
z1, z2 = zc - slot_L/2.0, zc + slot_L/2.0
prim = air.AddBox([x1, b, z1], [x2, b+t_metal, z2])
@@ -181,7 +181,7 @@ if simulate:
# Post-processing: S-params & impedance
# -------------------------
freq = np.linspace(f_start, f_stop, 401)
ports = [p for p in FDTD.ports] # Port 1 & Port 2 in creation order
ports = list(FDTD.ports) # Port 1 & Port 2 in creation order
for p in ports:
p.CalcPort(Sim_Path, freq)
@@ -226,9 +226,6 @@ mismatch = 1.0 - np.abs(S11[idx_f0])**2 # (1 - |S11|^2)
Gmax_lin = Dmax_lin * float(mismatch)
Gmax_dBi = 10*np.log10(Gmax_lin)
print(f"Max directivity @ {f0/1e9:.3f} GHz: {10*np.log10(Dmax_lin):.2f} dBi")
print(f"Mismatch term (1-|S11|^2) : {float(mismatch):.3f}")
print(f"Estimated max realized gain : {Gmax_dBi:.2f} dBi")
# 3D normalized pattern
E = np.squeeze(res.E_norm) # shape [f, th, ph] -> [th, ph]
@@ -254,7 +251,7 @@ plt.figure(figsize=(8.4,2.8))
plt.fill_between(
[0, a], [0, 0], [L, L], color='#dddddd', alpha=0.5, step='pre', label='WG aperture (top)'
)
for zc, xc in zip(z_centers, x_centers):
for zc, xc in zip(z_centers, x_centers, strict=False):
plt.gca().add_patch(plt.Rectangle((xc - slot_w/2.0, zc - slot_L/2.0),
slot_w, slot_L, fc='#3355ff', ec='k'))
plt.xlim(-2, a + 2)
@@ -1,6 +1,6 @@
# openems_quartz_slotted_wg_10p5GHz.py
# Slotted rectangular waveguide (quartz-filled, εr=3.8) tuned to 10.5 GHz.
# Builds geometry, meshes (no GetLine calls), sweeps S-params/impedance over 9.511.5 GHz,
# Builds geometry, meshes (no GetLine calls), sweeps S-params/impedance over 9.5-11.5 GHz,
# computes 3D far-field, and reports estimated max realized gain.
import os
@@ -15,14 +15,14 @@ from openEMS.physical_constants import C0
try:
from CSXCAD import ContinuousStructure, AppCSXCAD_BIN
HAVE_APP = True
except Exception:
except ImportError:
from CSXCAD import ContinuousStructure
AppCSXCAD_BIN = None
HAVE_APP = False
#Set PROFILE to "sanity" first; run and check [mesh] cells: stays reasonable.
#If its small, move to "balanced"; once happy, go "full".
#If it's small, move to "balanced"; once happy, go "full".
#Toggle VIEW_GEOM=True if you want the 3D viewer (requires AppCSXCAD_BIN available).
@@ -123,9 +123,9 @@ x_edges = np.concatenate([x_centers - slot_w/2.0, x_centers + slot_w/2.0])
z_edges = np.concatenate([z_centers - slot_L/2.0, z_centers + slot_L/2.0])
# Mesh lines: explicit (NO GetLine calls)
x_lines = sorted(set([x_min, -t_metal, 0.0, a, a+t_metal, x_max] + list(x_edges)))
x_lines = sorted({x_min, -t_metal, 0.0, a, a + t_metal, x_max, *list(x_edges)})
y_lines = [y_min, 0.0, b, b+t_metal, y_max]
z_lines = sorted(set([z_min, 0.0, guide_length_mm, z_max] + list(z_edges)))
z_lines = sorted({z_min, 0.0, guide_length_mm, z_max, *list(z_edges)})
mesh.AddLine('x', x_lines)
mesh.AddLine('y', y_lines)
@@ -134,13 +134,10 @@ mesh.AddLine('z', z_lines)
# Print complexity and rough memory (to help stay inside 16 GB)
Nx, Ny, Nz = len(x_lines)-1, len(y_lines)-1, len(z_lines)-1
Ncells = Nx*Ny*Nz
print(f"[mesh] cells: {Nx} × {Ny} × {Nz} = {Ncells:,}")
mem_fields_bytes = Ncells * 6 * 8 # rough ~ (Ex,Ey,Ez,Hx,Hy,Hz) doubles
print(f"[mesh] rough field memory: ~{mem_fields_bytes/1e9:.2f} GB (solver overhead extra)")
dx_min = min(np.diff(x_lines))
dy_min = min(np.diff(y_lines))
dz_min = min(np.diff(z_lines))
print(f"[mesh] min steps (mm): dx={dx_min:.3f}, dy={dy_min:.3f}, dz={dz_min:.3f}")
# Optional smoothing to limit max cell size
mesh.SmoothMeshLines('all', mesh_res, ratio=1.4)
@@ -165,7 +162,7 @@ pec.AddBox(
) # top (slots will pierce)
# Slots (AIR) overriding top metal
for zc, xc in zip(z_centers, x_centers):
for zc, xc in zip(z_centers, x_centers, strict=False):
x1, x2 = xc - slot_w/2.0, xc + slot_w/2.0
z1, z2 = zc - slot_L/2.0, zc + slot_L/2.0
prim = airM.AddBox([x1, b, z1], [x2, b+t_metal, z2])
@@ -215,7 +212,6 @@ if VIEW_GEOM and HAVE_APP and AppCSXCAD_BIN:
t0 = time.time()
FDTD.Run(Sim_Path, cleanup=True, verbose=2, numThreads=THREADS)
t1 = time.time()
print(f"[timing] FDTD solve elapsed: {t1 - t0:.2f} s")
# ... right before NF2FF (far-field):
t2 = time.time()
@@ -224,14 +220,12 @@ try:
except AttributeError:
res = FDTD.CalcNF2FF(nf2ff, Sim_Path, [f0], theta, phi) # noqa: F821
t3 = time.time()
print(f"[timing] NF2FF (far-field) elapsed: {t3 - t2:.2f} s")
# ... S-parameters postproc timing (optional):
t4 = time.time()
for p in ports: # noqa: F821
p.CalcPort(Sim_Path, freq) # noqa: F821
t5 = time.time()
print(f"[timing] Port/S-params postproc elapsed: {t5 - t4:.2f} s")
# =======
@@ -240,11 +234,8 @@ print(f"[timing] Port/S-params postproc elapsed: {t5 - t4:.2f} s")
if SIMULATE:
FDTD.Run(Sim_Path, cleanup=True, verbose=2, numThreads=THREADS)
# ==========================
# POST: S-PARAMS / IMPEDANCE
# ==========================
freq = np.linspace(f_start, f_stop, profiles[PROFILE]["freq_pts"])
ports = [p for p in FDTD.ports] # Port 1 & 2 in creation order
freq = np.linspace(f_start, f_stop, profiles[PROFILE]["freq_pts"])
ports = list(FDTD.ports) # Port 1 & 2 in creation order
for p in ports:
p.CalcPort(Sim_Path, freq)
@@ -288,9 +279,6 @@ mismatch = 1.0 - np.abs(S11[idx_f0])**2
Gmax_lin = Dmax_lin * float(mismatch)
Gmax_dBi = 10*np.log10(Gmax_lin)
print(f"[far-field] Dmax @ {f0/1e9:.3f} GHz: {10*np.log10(Dmax_lin):.2f} dBi")
print(f"[far-field] mismatch (1-|S11|^2): {float(mismatch):.3f}")
print(f"[far-field] est. max realized gain: {Gmax_dBi:.2f} dBi")
# Normalized 3D pattern
E = np.squeeze(res.E_norm) # [th, ph]
@@ -324,7 +312,7 @@ plt.fill_between(
step='pre',
label='WG top aperture',
)
for zc, xc in zip(z_centers, x_centers):
for zc, xc in zip(z_centers, x_centers, strict=False):
plt.gca().add_patch(plt.Rectangle((xc - slot_w/2.0, zc - slot_L/2.0),
slot_w, slot_L, fc='#3355ff', ec='k'))
plt.xlim(-2, a + 2)
@@ -68,14 +68,8 @@ def generate_multi_ramp_csv(Fs=125e6, Tb=1e-6, Tau=2e-6, fmax=30e6, fmin=10e6,
# --- Save CSV (no header)
df = pd.DataFrame({"time(s)": t_csv, "voltage(V)": y_csv})
df.to_csv(filename, index=False, header=False)
print(f"CSV saved: {filename}")
print(
f"Total raw samples: {total_samples} | Ramps inserted: {ramps_inserted} "
f"| CSV points: {len(y_csv)}"
)
# --- Plot (staircase)
if show_plot or save_plot_png:
if show_plot or save_plot_png:
# Choose plotting vectors (use raw DAC samples to keep lines crisp)
t_plot = t
y_plot = y
@@ -111,7 +105,6 @@ def generate_multi_ramp_csv(Fs=125e6, Tb=1e-6, Tau=2e-6, fmax=30e6, fmin=10e6,
if save_plot_png:
plt.savefig(save_plot_png, dpi=150)
print(f"Plot saved: {save_plot_png}")
if show_plot:
plt.show()
else:
+1 -2
View File
@@ -1,7 +1,6 @@
import matplotlib.pyplot as plt
# Dimensions (all in mm)
line_width = 0.204
line_width = 0.204
substrate_height = 0.102
via_drill = 0.20
via_pad_A = 0.20 # minimal pad case
+3 -4
View File
@@ -1,7 +1,6 @@
import matplotlib.pyplot as plt
# Dimensions (all in mm)
line_width = 0.204
line_width = 0.204
via_pad_A = 0.20
via_pad_B = 0.45
polygon_offset = 0.30
@@ -50,14 +49,14 @@ ax.text(-2, polygon_y1 + 0.5, "Via B Ø0.45 mm pad", color="red")
# Add pitch dimension (horizontal between vias)
ax.annotate("", xy=(2, polygon_y1 + 0.2), xytext=(2 + via_pitch, polygon_y1 + 0.2),
arrowprops=dict(arrowstyle="<->", color="purple"))
arrowprops={"arrowstyle": "<->", "color": "purple"})
ax.text(2 + via_pitch/2, polygon_y1 + 0.3, f"{via_pitch:.2f} mm pitch", color="purple", ha="center")
# Add distance from RF line edge to via center
line_edge_y = rf_line_y + line_width/2
via_center_y = polygon_y1
ax.annotate("", xy=(2.4, line_edge_y), xytext=(2.4, via_center_y),
arrowprops=dict(arrowstyle="<->", color="brown"))
arrowprops={"arrowstyle": "<->", "color": "brown"})
ax.text(
2.5, (line_edge_y + via_center_y) / 2, f"{via_center_offset:.2f} mm", color="brown", va="center"
)
@@ -27,7 +27,7 @@ n_idx = np.arange(N) - (N-1)/2
y_positions = m_idx * dy
z_positions = n_idx * dz
def element_factor(theta_rad, phi_rad):
def element_factor(theta_rad, _phi_rad):
return np.abs(np.cos(theta_rad))
def array_factor(theta_rad, phi_rad, y_positions, z_positions, wy, wz, theta0_rad, phi0_rad):
@@ -105,8 +105,3 @@ plt.title('Array Pattern Heatmap (|AF·EF|, dB) — Kaiser ~-25 dB')
plt.tight_layout()
plt.savefig('Heatmap_Kaiser25dB_like.png', bbox_inches='tight')
plt.show()
print(
'Saved: E_plane_Kaiser25dB_like.png, H_plane_Kaiser25dB_like.png, '
'Heatmap_Kaiser25dB_like.png'
)