witwin-maxwell 0.3.0__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. witwin/maxwell/__init__.py +164 -0
  2. witwin/maxwell/adapters/__init__.py +1 -0
  3. witwin/maxwell/adapters/gds.py +203 -0
  4. witwin/maxwell/adapters/tidy3d.py +1669 -0
  5. witwin/maxwell/adjoint_inputs.py +70 -0
  6. witwin/maxwell/compiler/__init__.py +11 -0
  7. witwin/maxwell/compiler/materials.py +1355 -0
  8. witwin/maxwell/compiler/monitors.py +164 -0
  9. witwin/maxwell/compiler/sources.py +222 -0
  10. witwin/maxwell/fdfd/__init__.py +3 -0
  11. witwin/maxwell/fdfd/adjoint/__init__.py +3 -0
  12. witwin/maxwell/fdfd/adjoint/bridge.py +180 -0
  13. witwin/maxwell/fdfd/ams.py +300 -0
  14. witwin/maxwell/fdfd/krylov.py +280 -0
  15. witwin/maxwell/fdfd/plotting.py +110 -0
  16. witwin/maxwell/fdfd/postprocess.py +33 -0
  17. witwin/maxwell/fdfd/solver.py +1057 -0
  18. witwin/maxwell/fdtd/__init__.py +4 -0
  19. witwin/maxwell/fdtd/adjoint/__init__.py +21 -0
  20. witwin/maxwell/fdtd/adjoint/bridge.py +746 -0
  21. witwin/maxwell/fdtd/adjoint/capabilities.py +29 -0
  22. witwin/maxwell/fdtd/adjoint/core.py +3614 -0
  23. witwin/maxwell/fdtd/adjoint/dispatch.py +684 -0
  24. witwin/maxwell/fdtd/adjoint/native.py +2561 -0
  25. witwin/maxwell/fdtd/adjoint/profiler.py +195 -0
  26. witwin/maxwell/fdtd/adjoint/reverse_common.py +207 -0
  27. witwin/maxwell/fdtd/adjoint/seeds.py +524 -0
  28. witwin/maxwell/fdtd/boundary/__init__.py +37 -0
  29. witwin/maxwell/fdtd/boundary/bloch.py +90 -0
  30. witwin/maxwell/fdtd/boundary/common.py +41 -0
  31. witwin/maxwell/fdtd/boundary/cpml.py +527 -0
  32. witwin/maxwell/fdtd/boundary/runtime.py +209 -0
  33. witwin/maxwell/fdtd/checkpoint.py +227 -0
  34. witwin/maxwell/fdtd/coords.py +32 -0
  35. witwin/maxwell/fdtd/cuda/__init__.py +11 -0
  36. witwin/maxwell/fdtd/cuda/backend.py +3532 -0
  37. witwin/maxwell/fdtd/cuda/build.py +292 -0
  38. witwin/maxwell/fdtd/cuda/extension.cpp +2053 -0
  39. witwin/maxwell/fdtd/cuda/kernels/adjoint.cu +5290 -0
  40. witwin/maxwell/fdtd/cuda/kernels/aniso.cu +1704 -0
  41. witwin/maxwell/fdtd/cuda/kernels/boundary.cu +424 -0
  42. witwin/maxwell/fdtd/cuda/kernels/common.cu +74 -0
  43. witwin/maxwell/fdtd/cuda/kernels/common.cuh +82 -0
  44. witwin/maxwell/fdtd/cuda/kernels/dispersive.cu +782 -0
  45. witwin/maxwell/fdtd/cuda/kernels/electric.cu +3613 -0
  46. witwin/maxwell/fdtd/cuda/kernels/magnetic.cu +781 -0
  47. witwin/maxwell/fdtd/cuda/kernels/observers.cu +347 -0
  48. witwin/maxwell/fdtd/cuda/kernels/projection.cu +211 -0
  49. witwin/maxwell/fdtd/cuda/kernels/sources.cu +1482 -0
  50. witwin/maxwell/fdtd/cuda/kernels/spectral.cu +297 -0
  51. witwin/maxwell/fdtd/cuda/launch.h +23 -0
  52. witwin/maxwell/fdtd/cuda/prebuilt/witwin_maxwell_fdtd_cuda.pyd +0 -0
  53. witwin/maxwell/fdtd/cuda/runtime/__init__.py +6 -0
  54. witwin/maxwell/fdtd/cuda/runtime/dispatcher.py +6 -0
  55. witwin/maxwell/fdtd/cuda/runtime/graph.py +33 -0
  56. witwin/maxwell/fdtd/cuda/tensors.h +35 -0
  57. witwin/maxwell/fdtd/dispersion.py +48 -0
  58. witwin/maxwell/fdtd/evaluation.py +290 -0
  59. witwin/maxwell/fdtd/excitation/__init__.py +27 -0
  60. witwin/maxwell/fdtd/excitation/injection.py +1102 -0
  61. witwin/maxwell/fdtd/excitation/modes.py +1675 -0
  62. witwin/maxwell/fdtd/excitation/spatial.py +469 -0
  63. witwin/maxwell/fdtd/excitation/temporal.py +510 -0
  64. witwin/maxwell/fdtd/excitation/tfsf_apply.py +264 -0
  65. witwin/maxwell/fdtd/excitation/tfsf_common.py +421 -0
  66. witwin/maxwell/fdtd/excitation/tfsf_specs.py +490 -0
  67. witwin/maxwell/fdtd/excitation/tfsf_state.py +778 -0
  68. witwin/maxwell/fdtd/io.py +31 -0
  69. witwin/maxwell/fdtd/material_pullback.py +371 -0
  70. witwin/maxwell/fdtd/meshing/__init__.py +3 -0
  71. witwin/maxwell/fdtd/meshing/autogrid.py +335 -0
  72. witwin/maxwell/fdtd/observers.py +1453 -0
  73. witwin/maxwell/fdtd/plotting.py +121 -0
  74. witwin/maxwell/fdtd/postprocess.py +205 -0
  75. witwin/maxwell/fdtd/runtime/__init__.py +119 -0
  76. witwin/maxwell/fdtd/runtime/initialization.py +236 -0
  77. witwin/maxwell/fdtd/runtime/materials.py +1563 -0
  78. witwin/maxwell/fdtd/runtime/module_cache.py +31 -0
  79. witwin/maxwell/fdtd/runtime/spectral.py +581 -0
  80. witwin/maxwell/fdtd/runtime/stepping.py +1987 -0
  81. witwin/maxwell/fdtd/solver.py +502 -0
  82. witwin/maxwell/geometry/__init__.py +5 -0
  83. witwin/maxwell/geometry/polyslab.py +299 -0
  84. witwin/maxwell/materials.py +3 -0
  85. witwin/maxwell/media.py +1467 -0
  86. witwin/maxwell/monitors.py +743 -0
  87. witwin/maxwell/ports.py +113 -0
  88. witwin/maxwell/postprocess/RCS.py +139 -0
  89. witwin/maxwell/postprocess/__init__.py +42 -0
  90. witwin/maxwell/postprocess/diffraction.py +219 -0
  91. witwin/maxwell/postprocess/directivity.py +152 -0
  92. witwin/maxwell/postprocess/emission.py +67 -0
  93. witwin/maxwell/postprocess/modal.py +324 -0
  94. witwin/maxwell/postprocess/nfft.py +266 -0
  95. witwin/maxwell/postprocess/scattering_parameters.py +358 -0
  96. witwin/maxwell/postprocess/stratton_chu.py +1193 -0
  97. witwin/maxwell/result.py +967 -0
  98. witwin/maxwell/scene.py +1338 -0
  99. witwin/maxwell/simulation.py +659 -0
  100. witwin/maxwell/sources.py +917 -0
  101. witwin/maxwell/visualization/__init__.py +28 -0
  102. witwin/maxwell/visualization/interactive.py +484 -0
  103. witwin/maxwell/visualization/plots.py +568 -0
  104. witwin_maxwell-0.3.0.dist-info/METADATA +184 -0
  105. witwin_maxwell-0.3.0.dist-info/RECORD +107 -0
  106. witwin_maxwell-0.3.0.dist-info/WHEEL +4 -0
  107. witwin_maxwell-0.3.0.dist-info/licenses/LICENSE +13 -0
@@ -0,0 +1,290 @@
1
+ import itertools
2
+
3
+ import numpy as np
4
+ import torch
5
+
6
+ import witwin.maxwell as mw
7
+ from .solver import calculate_required_steps
8
+
9
+
10
+ def build_empty_scene(
11
+ *,
12
+ domain_half,
13
+ grid,
14
+ device,
15
+ pml_thickness,
16
+ pml_strength,
17
+ source_width=0.05,
18
+ source_amplitude=100.0,
19
+ polarization=(0.0, 0.0, 1.0),
20
+ source_frequency=1e9,
21
+ ):
22
+ resolution = (2.0 * domain_half) / grid
23
+ scene = mw.Scene(
24
+ domain=mw.Domain(
25
+ bounds=(
26
+ (-domain_half, domain_half),
27
+ (-domain_half, domain_half),
28
+ (-domain_half, domain_half),
29
+ )
30
+ ),
31
+ grid=mw.GridSpec.uniform(resolution),
32
+ boundary=mw.BoundarySpec.pml(num_layers=pml_thickness, strength=pml_strength),
33
+ device=device,
34
+ )
35
+ scene.add_source(
36
+ mw.PointDipole(
37
+ position=(0.0, 0.0, 0.0),
38
+ polarization=polarization,
39
+ width=source_width,
40
+ source_time=mw.CW(frequency=source_frequency, amplitude=source_amplitude),
41
+ name="src",
42
+ )
43
+ )
44
+ return scene
45
+
46
+
47
+ def compute_steps(scene, dt, c, frequency, num_cycles=20, transient_cycles=20):
48
+ bounds = scene.domain.bounds
49
+ domain_size = max(
50
+ bounds[0][1] - bounds[0][0],
51
+ bounds[1][1] - bounds[1][0],
52
+ bounds[2][1] - bounds[2][0],
53
+ )
54
+ return calculate_required_steps(
55
+ frequency=frequency,
56
+ dt=dt,
57
+ c=c,
58
+ num_cycles=num_cycles,
59
+ transient_cycles=transient_cycles,
60
+ domain_size=domain_size,
61
+ )
62
+
63
+
64
+ def run_empty_scene_solver(
65
+ *,
66
+ domain_half,
67
+ grid,
68
+ frequency,
69
+ absorber_type,
70
+ cpml_config,
71
+ pml_thickness,
72
+ pml_strength,
73
+ device,
74
+ source_width=0.05,
75
+ source_amplitude=100.0,
76
+ num_cycles=20,
77
+ transient_cycles=20,
78
+ ):
79
+ scene = build_empty_scene(
80
+ domain_half=domain_half,
81
+ grid=grid,
82
+ device=device,
83
+ pml_thickness=pml_thickness,
84
+ pml_strength=pml_strength,
85
+ source_width=source_width,
86
+ source_amplitude=source_amplitude,
87
+ source_frequency=frequency,
88
+ )
89
+ sim = mw.Simulation.fdtd(
90
+ scene,
91
+ frequency=frequency,
92
+ absorber=absorber_type,
93
+ cpml_config=cpml_config,
94
+ run_time=mw.TimeConfig.auto(steady_cycles=num_cycles, transient_cycles=transient_cycles),
95
+ spectral_sampler=mw.SpectralSampler(window="hanning"),
96
+ )
97
+ prepared = sim.prepare()
98
+ steps = compute_steps(
99
+ scene,
100
+ prepared.solver.dt,
101
+ prepared.solver.c,
102
+ frequency,
103
+ num_cycles=num_cycles,
104
+ transient_cycles=transient_cycles,
105
+ )
106
+ prepared.simulation.config.run_time = mw.TimeConfig(time_steps=steps)
107
+ result = prepared.run()
108
+ field_mag, x_coords, y_coords, z_coords = result.solver._interpolate_yee_to_center(result.raw_output)
109
+ return {
110
+ "field": field_mag,
111
+ "x": x_coords,
112
+ "y": y_coords,
113
+ "z": z_coords,
114
+ "steps": steps,
115
+ }
116
+
117
+
118
+ def crop_reference_field(reference_field, reference_coords, target_coords):
119
+ start = int(np.argmin(np.abs(reference_coords - target_coords[0])))
120
+ end = start + target_coords.shape[0]
121
+ return reference_field[start:end]
122
+
123
+
124
+ def extract_aligned_reference(reference, target):
125
+ ref_field = reference["field"]
126
+ x_start = int(np.argmin(np.abs(reference["x"] - target["x"][0])))
127
+ y_start = int(np.argmin(np.abs(reference["y"] - target["y"][0])))
128
+ z_start = int(np.argmin(np.abs(reference["z"] - target["z"][0])))
129
+ x_end = x_start + target["field"].shape[0]
130
+ y_end = y_start + target["field"].shape[1]
131
+ z_end = z_start + target["field"].shape[2]
132
+ return ref_field[x_start:x_end, y_start:y_end, z_start:z_end]
133
+
134
+
135
+ def evaluate_reference_error(
136
+ target,
137
+ reference,
138
+ *,
139
+ pml_thickness,
140
+ source_exclusion_cells=2,
141
+ ):
142
+ ref_crop = extract_aligned_reference(reference, target)
143
+ field = target["field"]
144
+ abs_diff = np.abs(field - ref_crop)
145
+
146
+ max_margin = max(min(field.shape) // 2 - 2, 0)
147
+ effective_margin = min(int(pml_thickness), max_margin)
148
+ if effective_margin == 0:
149
+ interior = (slice(None),) * 3
150
+ else:
151
+ interior = (slice(effective_margin, -effective_margin),) * 3
152
+ field_interior = field[interior]
153
+ ref_interior = ref_crop[interior]
154
+ diff_interior = abs_diff[interior]
155
+
156
+ if field_interior.size == 0:
157
+ field_interior = field
158
+ ref_interior = ref_crop
159
+ diff_interior = abs_diff
160
+
161
+ center = np.array(field.shape) // 2
162
+ grid = np.indices(field_interior.shape).transpose(1, 2, 3, 0)
163
+ shifted = grid - (np.array(field_interior.shape) // 2)
164
+ radius_sq = np.sum(shifted**2, axis=-1)
165
+ exclusion_radius = min(source_exclusion_cells, max(min(field_interior.shape) // 4, 0))
166
+ keep_mask = radius_sq >= exclusion_radius**2
167
+
168
+ field_masked = field_interior[keep_mask]
169
+ ref_masked = ref_interior[keep_mask]
170
+ diff_masked = diff_interior[keep_mask]
171
+ if diff_masked.size == 0:
172
+ field_masked = field_interior.reshape(-1)
173
+ ref_masked = ref_interior.reshape(-1)
174
+ diff_masked = diff_interior.reshape(-1)
175
+
176
+ rel_l2 = float(np.linalg.norm(diff_masked) / max(np.linalg.norm(ref_masked), 1e-12))
177
+ rel_linf = float(np.max(diff_masked) / max(np.max(np.abs(ref_masked)), 1e-12))
178
+
179
+ cx, cy, cz = center
180
+ if effective_margin == 0:
181
+ line_slice = slice(None)
182
+ else:
183
+ line_slice = slice(effective_margin, field.shape[0] - effective_margin)
184
+ axis_target = field[line_slice, cy, cz]
185
+ axis_ref = ref_crop[line_slice, cy, cz]
186
+ axis_rel_l2 = float(np.linalg.norm(axis_target - axis_ref) / max(np.linalg.norm(axis_ref), 1e-12))
187
+
188
+ shell_start = min(effective_margin, max(field.shape[0] - 2, 0))
189
+ shell_end = min(shell_start + 2, field.shape[0])
190
+ shell = field[shell_start:shell_end, :, :]
191
+ shell_mean = float(np.mean(shell)) if shell.size > 0 else 0.0
192
+ core_half = max(field.shape[0] // 4, 2)
193
+ core = field[
194
+ max(cx - core_half // 2, 0) : min(cx + core_half // 2, field.shape[0]),
195
+ max(cy - core_half // 2, 0) : min(cy + core_half // 2, field.shape[1]),
196
+ max(cz - core_half // 2, 0) : min(cz + core_half // 2, field.shape[2]),
197
+ ]
198
+ core_mean = float(np.mean(core))
199
+ shell_to_core = shell_mean / max(core_mean, 1e-12)
200
+
201
+ return {
202
+ "reference_rel_l2": rel_l2,
203
+ "reference_rel_linf": rel_linf,
204
+ "axis_rel_l2": axis_rel_l2,
205
+ "shell_to_core_ratio": shell_to_core,
206
+ "max_abs_diff": float(np.max(abs_diff)),
207
+ "mean_abs_diff": float(np.mean(abs_diff)),
208
+ "reference_field": ref_crop,
209
+ "abs_diff_field": abs_diff,
210
+ }
211
+
212
+
213
+ def generate_candidate_configs():
214
+ grading_orders = [3.0, 4.0]
215
+ kappa_max_values = [5.0, 8.0, 11.0]
216
+ alpha_max_values = [0.02, 0.05, 0.10]
217
+ reflections = [1e-6, 1e-8]
218
+ for grading_order, kappa_max, alpha_max, reflection in itertools.product(
219
+ grading_orders,
220
+ kappa_max_values,
221
+ alpha_max_values,
222
+ reflections,
223
+ ):
224
+ yield {
225
+ "grading_order": grading_order,
226
+ "kappa_max": kappa_max,
227
+ "alpha_max": alpha_max,
228
+ "reflection": reflection,
229
+ }
230
+
231
+
232
+ def tune_cpml_against_reference(
233
+ *,
234
+ grid,
235
+ domain_half,
236
+ reference_scale,
237
+ frequency,
238
+ pml_thickness,
239
+ pml_strength,
240
+ device,
241
+ candidate_configs=None,
242
+ num_cycles=20,
243
+ transient_cycles=20,
244
+ ):
245
+ if candidate_configs is None:
246
+ candidate_configs = list(generate_candidate_configs())
247
+
248
+ reference = run_empty_scene_solver(
249
+ domain_half=domain_half * reference_scale,
250
+ grid=int(round(grid * reference_scale)),
251
+ frequency=frequency,
252
+ absorber_type="cpml",
253
+ cpml_config=None,
254
+ pml_thickness=pml_thickness,
255
+ pml_strength=pml_strength,
256
+ device=device,
257
+ num_cycles=num_cycles,
258
+ transient_cycles=transient_cycles,
259
+ )
260
+
261
+ results = []
262
+ for config in candidate_configs:
263
+ target = run_empty_scene_solver(
264
+ domain_half=domain_half,
265
+ grid=grid,
266
+ frequency=frequency,
267
+ absorber_type="cpml",
268
+ cpml_config=config,
269
+ pml_thickness=pml_thickness,
270
+ pml_strength=pml_strength,
271
+ device=device,
272
+ num_cycles=num_cycles,
273
+ transient_cycles=transient_cycles,
274
+ )
275
+ metrics = evaluate_reference_error(target, reference, pml_thickness=pml_thickness)
276
+ score = metrics["reference_rel_l2"] + 0.5 * metrics["axis_rel_l2"] + 0.1 * metrics["shell_to_core_ratio"]
277
+ results.append(
278
+ {
279
+ "config": dict(config),
280
+ "score": float(score),
281
+ "metrics": {k: v for k, v in metrics.items() if not isinstance(v, np.ndarray)},
282
+ }
283
+ )
284
+
285
+ results.sort(key=lambda item: item["score"])
286
+ return {
287
+ "reference": reference,
288
+ "results": results,
289
+ "best": results[0],
290
+ }
@@ -0,0 +1,27 @@
1
+ from .injection import (
2
+ initialize_source_terms,
3
+ inject_electric_surface_source_terms,
4
+ inject_magnetic_surface_source_terms,
5
+ inject_source_terms,
6
+ )
7
+ from .tfsf_apply import (
8
+ advance_tfsf_auxiliary_electric,
9
+ advance_tfsf_auxiliary_magnetic,
10
+ apply_tfsf_e_correction,
11
+ apply_tfsf_h_correction,
12
+ tfsf_incident_is_gpu_driven,
13
+ )
14
+ from .tfsf_state import initialize_tfsf_state
15
+
16
+ __all__ = [
17
+ "advance_tfsf_auxiliary_electric",
18
+ "advance_tfsf_auxiliary_magnetic",
19
+ "apply_tfsf_e_correction",
20
+ "apply_tfsf_h_correction",
21
+ "initialize_source_terms",
22
+ "initialize_tfsf_state",
23
+ "inject_electric_surface_source_terms",
24
+ "inject_magnetic_surface_source_terms",
25
+ "inject_source_terms",
26
+ "tfsf_incident_is_gpu_driven",
27
+ ]