-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfull_integration_test.py
More file actions
273 lines (221 loc) · 8.94 KB
/
full_integration_test.py
File metadata and controls
273 lines (221 loc) · 8.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
"""
test.py - Full Integration Test
This script demonstrates the complete workflow:
1. Design patch antenna
2. Setup simulation
3. Run FDTD simulation
4. Post-process results
5. Generate plots and export data
This is a working example that can be used as a template for
custom antenna designs.
"""
from core.executor import SimRunner
from core.patch_antenna_design import (
InputParameters,
CalculatedParameters,
_calculateInitPatchWidth,
_calculateSubstrateEpsREff,
_calculateInitPatchFringing,
_calculateInitEffPatchLength,
_calculatePatchGuidedWavelength,
_calculateInitPatchLength,
_estimateInitPatchG1,
_estimateInitPatchB1,
_calculatePatchG1,
_calculatePatchB1,
_calculateCharacteristicImpedance,
_calculateCharacteristicAdmittance,
_calculateMutualConductance,
_calculateRIn,
_calculatePatchInsetFeedDepth,
_calculateMSFeedlineWidth,
_calculateInsetNotchWidthRange,
_calculateWavelength,
_calculate_k0,
)
from core.openems_simulation_prep import SimulationSetup
from core.post_processor import PostProcessor
from pathlib import Path
import numpy as np
from openEMS.physical_constants import C0, EPS0, MUE0
def design_patch_antenna(params: InputParameters) -> CalculatedParameters:
"""
Run all design calculations in proper sequence
Args:
params: InputParameters with design specifications
Returns:
CalculatedParameters with all computed values
"""
print("\n" + "="*60)
print("PATCH ANTENNA DESIGN CALCULATIONS")
print("="*60)
# Initialize calculated parameters
calc = CalculatedParameters()
calc.ledger = {} # Initialize ledger
calc.ledger['Z_c_feed'] = params.z_in
# Run all calculations in sequence
print("\nCalculating patch dimensions...")
_calculateInitPatchWidth(params, calc)
_calculateSubstrateEpsREff(params, calc)
_calculateInitPatchFringing(params, calc)
_calculateInitEffPatchLength(params, calc)
_calculatePatchGuidedWavelength(calc)
_calculateInitPatchLength(calc)
_calculateWavelength(params, calc)
_calculate_k0(calc)
calc.ledger['eta_0'] = np.sqrt(MUE0 / EPS0)
print("Calculating radiation parameters...")
_estimateInitPatchG1(params, calc)
_estimateInitPatchB1(params, calc)
_calculatePatchG1(params, calc)
_calculatePatchB1(calc)
print("Calculating feed parameters...")
_calculateCharacteristicImpedance(params, calc)
_calculateCharacteristicAdmittance(calc)
_calculateMutualConductance(params, calc)
_calculateRIn(calc)
_calculatePatchInsetFeedDepth(calc)
_calculateMSFeedlineWidth(params, calc)
_calculateInsetNotchWidthRange(calc)
# Print summary
print("\n" + "="*60)
print("DESIGN SUMMARY")
print("="*60)
print(f"Patch Length: {calc.ledger['L_phys']*1000:.3f} mm")
print(f"Patch Width: {calc.ledger['width']*1000:.3f} mm")
print(f"Feed Depth: {calc.ledger['y0']*1000:.3f} mm")
print(f"Feed Width: {calc.ledger['W0']*1000:.3f} mm")
print(f"Notch Width: {calc.ledger['notch_width']*1000:.3f} mm")
print(f"Input Resistance: {calc.ledger['R_in']:.2f} Ω")
print("="*60 + "\n")
return calc
def main():
"""
Main workflow: Design → Setup → Simulate → Post-process
"""
# ==================== STEP 1: Define Design Parameters ====================
print("\n" + "="*60)
print("STEP 1: DEFINE DESIGN PARAMETERS")
print("="*60)
params = InputParameters(
freq=24.125e9, # 24.125 GHz (ISM band)
eps_r=3.48, # Rogers RO4350BC substrate
tan_d=0.003, # Loss tangent
thickness=0.762e-3, # 0.762 mm (30 mil)
z_in=50.0 # 50 Ω input impedance
)
print(f"Frequency: {params.freq/1e9:.3f} GHz")
print(f"Substrate: Rogers RO4003C (εr = {params.eps_r})")
print(f"Thickness: {params.thickness*1000:.3f} mm")
print(f"Target Impedance: {params.z_in:.0f} Ω")
# ==================== STEP 2: Calculate Antenna Dimensions ====================
print("\n" + "="*60)
print("STEP 2: CALCULATE ANTENNA DIMENSIONS")
print("="*60)
calc = design_patch_antenna(params)
# ==================== STEP 3: Setup Simulation ====================
print("\n" + "="*60)
print("STEP 3: SETUP SIMULATION")
print("="*60)
simsetup = SimulationSetup(
params,
calc,
sim_name="patch_24GHz_test",
mesh_density="coarse", # Use "coarse" for testing, "fine" for production
use_temp_dir=False # Save in ./simulations/ directory
)
# Finalize setup (creates geometry, mesh, port, nf2ff)
simsetup.finalize_setup()
# Print simulation info
sim_info = simsetup.get_simulation_info()
print("\nSimulation Configuration:")
print(f" Name: {sim_info['simulation_name']}")
print(f" Path: {sim_info['simulation_path']}")
print(f" Frequency Range: {sim_info['frequency_range'][0]/1e9:.2f} - {sim_info['frequency_range'][1]/1e9:.2f} GHz")
print(f" Mesh Density: {sim_info['mesh_density']}")
print(f" Mesh Resolution: {sim_info['mesh_resolution_mm']:.3f} mm")
# ==================== STEP 4: Run Simulation ====================
print("\n" + "="*60)
print("STEP 4: RUN FDTD SIMULATION")
print("="*60)
# Create runner
runner = SimRunner(simsetup)
# Run simulation (this will take some time depending on mesh density)
results = runner.run(verbose=3, cleanup=True)
# Check if simulation was successful
if not results.success:
print(f"\nSimulation FAILED: {results.error_message}")
return
print("\n Simulation completed successfully!")
# Quick summary
summary = runner.get_quick_summary()
print("\nQuick Results:")
for key, value in summary.items():
print(f" {key}: {value}")
# Save results for later use
results_file = Path(simsetup.sim_path) / "simulation_results.npz"
runner.save_results(results_file)
# ==================== STEP 5: Post-Process Results ====================
print("\n" + "="*60)
print("STEP 5: POST-PROCESS RESULTS")
print("="*60)
# Create post-processor
processor = PostProcessor(results, z0=50.0)
# Process all data
processed = processor.process_all()
# ==================== STEP 6: Generate Plots ====================
print("\n" + "="*60)
print("STEP 6: GENERATE PLOTS")
print("="*60)
# Create output directory for plots
plot_dir = Path(simsetup.sim_path) / "plots"
plot_dir.mkdir(exist_ok=True)
# Generate all plots
processor.plot_all(plot_dir, show=False)
print(f"\n✓ All plots saved to: {plot_dir}")
# ==================== STEP 7: Export Data ====================
print("\n" + "="*60)
print("STEP 7: EXPORT DATA")
print("="*60)
# Export S-parameters to Touchstone format
touchstone_file = Path(simsetup.sim_path) / f"{simsetup.sim_name}.s1p"
processor.export_to_touchstone(touchstone_file)
# ==================== FINAL SUMMARY ====================
print("\n" + "="*60)
print("WORKFLOW COMPLETE - FINAL SUMMARY")
print("="*60)
print(f"\nOutput Directory: {simsetup.sim_path}")
print(f"\nKey Results:")
print(f" Resonant Frequency: {processed.resonant_frequency/1e9:.4f} GHz")
print(f" S11 at Resonance: {processed.min_s11_db:.2f} dB")
if processed.bandwidth_10db:
print(f" Bandwidth (-10dB): {processed.bandwidth_10db/1e6:.2f} MHz")
if processed.bandwidth_3db:
print(f" Bandwidth (-3dB): {processed.bandwidth_3db/1e6:.2f} MHz")
if processed.q_factor:
print(f" Quality Factor (Q): {processed.q_factor:.2f}")
# Impedance at resonance
res_idx = processed.resonant_frequency_idx
print(f"\n Input Impedance @ Resonance:")
print(f" Z_in = {processed.z_real[res_idx]:.2f} + j{processed.z_imag[res_idx]:.2f} Ω")
print(f" VSWR = {processed.vswr[res_idx]:.2f}")
# Radiation characteristics (if available)
if processed.has_nf2ff_data:
print(f"\n Radiation Characteristics:")
print(f" Max Directivity: {processed.max_directivity_dbi:.2f} dBi")
if processed.hpbw_e_plane:
print(f" HPBW (E-plane): {processed.hpbw_e_plane:.1f}°")
if processed.hpbw_h_plane:
print(f" HPBW (H-plane): {processed.hpbw_h_plane:.1f}°")
if processed.front_to_back_ratio:
print(f" Front-to-Back: {processed.front_to_back_ratio:.1f} dB")
print(f"\nGenerated Files:")
print(f" Simulation results: {results_file}")
print(f" S-parameters: {touchstone_file}")
print(f" Plots directory: {plot_dir}")
print("\n" + "="*60)
print(" COMPLETE SUCCESS")
print("="*60 + "\n")
if __name__ == "__main__":
# Run the complete workflow
main()