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.
- witwin/maxwell/__init__.py +164 -0
- witwin/maxwell/adapters/__init__.py +1 -0
- witwin/maxwell/adapters/gds.py +203 -0
- witwin/maxwell/adapters/tidy3d.py +1669 -0
- witwin/maxwell/adjoint_inputs.py +70 -0
- witwin/maxwell/compiler/__init__.py +11 -0
- witwin/maxwell/compiler/materials.py +1355 -0
- witwin/maxwell/compiler/monitors.py +164 -0
- witwin/maxwell/compiler/sources.py +222 -0
- witwin/maxwell/fdfd/__init__.py +3 -0
- witwin/maxwell/fdfd/adjoint/__init__.py +3 -0
- witwin/maxwell/fdfd/adjoint/bridge.py +180 -0
- witwin/maxwell/fdfd/ams.py +300 -0
- witwin/maxwell/fdfd/krylov.py +280 -0
- witwin/maxwell/fdfd/plotting.py +110 -0
- witwin/maxwell/fdfd/postprocess.py +33 -0
- witwin/maxwell/fdfd/solver.py +1057 -0
- witwin/maxwell/fdtd/__init__.py +4 -0
- witwin/maxwell/fdtd/adjoint/__init__.py +21 -0
- witwin/maxwell/fdtd/adjoint/bridge.py +746 -0
- witwin/maxwell/fdtd/adjoint/capabilities.py +29 -0
- witwin/maxwell/fdtd/adjoint/core.py +3614 -0
- witwin/maxwell/fdtd/adjoint/dispatch.py +684 -0
- witwin/maxwell/fdtd/adjoint/native.py +2561 -0
- witwin/maxwell/fdtd/adjoint/profiler.py +195 -0
- witwin/maxwell/fdtd/adjoint/reverse_common.py +207 -0
- witwin/maxwell/fdtd/adjoint/seeds.py +524 -0
- witwin/maxwell/fdtd/boundary/__init__.py +37 -0
- witwin/maxwell/fdtd/boundary/bloch.py +90 -0
- witwin/maxwell/fdtd/boundary/common.py +41 -0
- witwin/maxwell/fdtd/boundary/cpml.py +527 -0
- witwin/maxwell/fdtd/boundary/runtime.py +209 -0
- witwin/maxwell/fdtd/checkpoint.py +227 -0
- witwin/maxwell/fdtd/coords.py +32 -0
- witwin/maxwell/fdtd/cuda/__init__.py +11 -0
- witwin/maxwell/fdtd/cuda/backend.py +3532 -0
- witwin/maxwell/fdtd/cuda/build.py +292 -0
- witwin/maxwell/fdtd/cuda/extension.cpp +2053 -0
- witwin/maxwell/fdtd/cuda/kernels/adjoint.cu +5290 -0
- witwin/maxwell/fdtd/cuda/kernels/aniso.cu +1704 -0
- witwin/maxwell/fdtd/cuda/kernels/boundary.cu +424 -0
- witwin/maxwell/fdtd/cuda/kernels/common.cu +74 -0
- witwin/maxwell/fdtd/cuda/kernels/common.cuh +82 -0
- witwin/maxwell/fdtd/cuda/kernels/dispersive.cu +782 -0
- witwin/maxwell/fdtd/cuda/kernels/electric.cu +3613 -0
- witwin/maxwell/fdtd/cuda/kernels/magnetic.cu +781 -0
- witwin/maxwell/fdtd/cuda/kernels/observers.cu +347 -0
- witwin/maxwell/fdtd/cuda/kernels/projection.cu +211 -0
- witwin/maxwell/fdtd/cuda/kernels/sources.cu +1482 -0
- witwin/maxwell/fdtd/cuda/kernels/spectral.cu +297 -0
- witwin/maxwell/fdtd/cuda/launch.h +23 -0
- witwin/maxwell/fdtd/cuda/prebuilt/witwin_maxwell_fdtd_cuda.pyd +0 -0
- witwin/maxwell/fdtd/cuda/runtime/__init__.py +6 -0
- witwin/maxwell/fdtd/cuda/runtime/dispatcher.py +6 -0
- witwin/maxwell/fdtd/cuda/runtime/graph.py +33 -0
- witwin/maxwell/fdtd/cuda/tensors.h +35 -0
- witwin/maxwell/fdtd/dispersion.py +48 -0
- witwin/maxwell/fdtd/evaluation.py +290 -0
- witwin/maxwell/fdtd/excitation/__init__.py +27 -0
- witwin/maxwell/fdtd/excitation/injection.py +1102 -0
- witwin/maxwell/fdtd/excitation/modes.py +1675 -0
- witwin/maxwell/fdtd/excitation/spatial.py +469 -0
- witwin/maxwell/fdtd/excitation/temporal.py +510 -0
- witwin/maxwell/fdtd/excitation/tfsf_apply.py +264 -0
- witwin/maxwell/fdtd/excitation/tfsf_common.py +421 -0
- witwin/maxwell/fdtd/excitation/tfsf_specs.py +490 -0
- witwin/maxwell/fdtd/excitation/tfsf_state.py +778 -0
- witwin/maxwell/fdtd/io.py +31 -0
- witwin/maxwell/fdtd/material_pullback.py +371 -0
- witwin/maxwell/fdtd/meshing/__init__.py +3 -0
- witwin/maxwell/fdtd/meshing/autogrid.py +335 -0
- witwin/maxwell/fdtd/observers.py +1453 -0
- witwin/maxwell/fdtd/plotting.py +121 -0
- witwin/maxwell/fdtd/postprocess.py +205 -0
- witwin/maxwell/fdtd/runtime/__init__.py +119 -0
- witwin/maxwell/fdtd/runtime/initialization.py +236 -0
- witwin/maxwell/fdtd/runtime/materials.py +1563 -0
- witwin/maxwell/fdtd/runtime/module_cache.py +31 -0
- witwin/maxwell/fdtd/runtime/spectral.py +581 -0
- witwin/maxwell/fdtd/runtime/stepping.py +1987 -0
- witwin/maxwell/fdtd/solver.py +502 -0
- witwin/maxwell/geometry/__init__.py +5 -0
- witwin/maxwell/geometry/polyslab.py +299 -0
- witwin/maxwell/materials.py +3 -0
- witwin/maxwell/media.py +1467 -0
- witwin/maxwell/monitors.py +743 -0
- witwin/maxwell/ports.py +113 -0
- witwin/maxwell/postprocess/RCS.py +139 -0
- witwin/maxwell/postprocess/__init__.py +42 -0
- witwin/maxwell/postprocess/diffraction.py +219 -0
- witwin/maxwell/postprocess/directivity.py +152 -0
- witwin/maxwell/postprocess/emission.py +67 -0
- witwin/maxwell/postprocess/modal.py +324 -0
- witwin/maxwell/postprocess/nfft.py +266 -0
- witwin/maxwell/postprocess/scattering_parameters.py +358 -0
- witwin/maxwell/postprocess/stratton_chu.py +1193 -0
- witwin/maxwell/result.py +967 -0
- witwin/maxwell/scene.py +1338 -0
- witwin/maxwell/simulation.py +659 -0
- witwin/maxwell/sources.py +917 -0
- witwin/maxwell/visualization/__init__.py +28 -0
- witwin/maxwell/visualization/interactive.py +484 -0
- witwin/maxwell/visualization/plots.py +568 -0
- witwin_maxwell-0.3.0.dist-info/METADATA +184 -0
- witwin_maxwell-0.3.0.dist-info/RECORD +107 -0
- witwin_maxwell-0.3.0.dist-info/WHEEL +4 -0
- witwin_maxwell-0.3.0.dist-info/licenses/LICENSE +13 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import torch
|
|
8
|
+
|
|
9
|
+
from ...scene import prepare_scene
|
|
10
|
+
from ...sources import evaluate_source_time
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_AXIS_TO_INDEX = {"x": 0, "y": 1, "z": 2}
|
|
14
|
+
|
|
15
|
+
# Relative spacing spread at or below which a tangential aperture window is
|
|
16
|
+
# treated as exactly uniform: the exact minimum cell spacing is returned
|
|
17
|
+
# bit-for-bit (equal to the global minimum used before), so uniform grids keep
|
|
18
|
+
# their numerical-dispersion phase correction unchanged.
|
|
19
|
+
_SOFT_PLANE_UNIFORM_RTOL = 1e-6
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _pml_margin(scene, axis: str, side: str, *, extra: int = 0) -> int:
|
|
23
|
+
base = scene.pml_thickness_for_face(axis, side)
|
|
24
|
+
return int(base) + int(extra)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def resolve_injection_axis(direction, injection_axis=None) -> str:
|
|
28
|
+
if injection_axis is not None:
|
|
29
|
+
axis = str(injection_axis).lower()
|
|
30
|
+
if axis not in _AXIS_TO_INDEX:
|
|
31
|
+
raise ValueError("injection_axis must be 'x', 'y', 'z', or None.")
|
|
32
|
+
axis_index = _AXIS_TO_INDEX[axis]
|
|
33
|
+
if abs(float(direction[axis_index])) <= 1e-6:
|
|
34
|
+
raise ValueError("direction must have a non-zero component along injection_axis.")
|
|
35
|
+
return axis
|
|
36
|
+
|
|
37
|
+
dominant_index = max(range(3), key=lambda index: abs(float(direction[index])))
|
|
38
|
+
if abs(float(direction[dominant_index])) <= 1e-6:
|
|
39
|
+
raise ValueError("direction must be non-zero.")
|
|
40
|
+
return "xyz"[dominant_index]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def source_plane_index(scene, axis: str, direction_component: float) -> int:
|
|
44
|
+
scene = prepare_scene(scene)
|
|
45
|
+
axis_index = _AXIS_TO_INDEX[axis]
|
|
46
|
+
domain_counts = (scene.Nx, scene.Ny, scene.Nz)
|
|
47
|
+
low_margin = min(_pml_margin(scene, axis, "low", extra=1), max(domain_counts[axis_index] - 1, 0))
|
|
48
|
+
high_margin = min(_pml_margin(scene, axis, "high", extra=1), max(domain_counts[axis_index] - 1, 0))
|
|
49
|
+
if direction_component >= 0.0:
|
|
50
|
+
return low_margin
|
|
51
|
+
return max(domain_counts[axis_index] - high_margin - 1, 0)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def physical_interior_indices(scene, axis: str) -> tuple[int, int]:
|
|
55
|
+
scene = prepare_scene(scene)
|
|
56
|
+
size = {"x": scene.Nx, "y": scene.Ny, "z": scene.Nz}[axis]
|
|
57
|
+
lo = _pml_margin(scene, axis, "low")
|
|
58
|
+
hi = size - _pml_margin(scene, axis, "high") - 1
|
|
59
|
+
if hi <= lo:
|
|
60
|
+
raise ValueError(f"Scene has no physical interior along {axis}.")
|
|
61
|
+
return lo, hi
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def soft_plane_wave_index(scene, axis: str, direction_component: float, *, fraction: float = 0.05) -> int:
|
|
65
|
+
scene = prepare_scene(scene)
|
|
66
|
+
axis_coords = {"x": scene.x, "y": scene.y, "z": scene.z}[axis]
|
|
67
|
+
lo, hi = physical_interior_indices(scene, axis)
|
|
68
|
+
lo_coord = float(axis_coords[lo].item())
|
|
69
|
+
hi_coord = float(axis_coords[hi].item())
|
|
70
|
+
span = hi_coord - lo_coord
|
|
71
|
+
placement = float(fraction)
|
|
72
|
+
if direction_component >= 0.0:
|
|
73
|
+
target = lo_coord + placement * span
|
|
74
|
+
else:
|
|
75
|
+
target = hi_coord - placement * span
|
|
76
|
+
return int(torch.argmin(torch.abs(axis_coords - target)).item())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def soft_plane_wave_coordinate(scene, axis: str, direction_component: float, *, fraction: float = 0.05) -> float:
|
|
80
|
+
scene = prepare_scene(scene)
|
|
81
|
+
index = soft_plane_wave_index(scene, axis, direction_component, fraction=fraction)
|
|
82
|
+
axis_coords = {"x": scene.x, "y": scene.y, "z": scene.z}[axis]
|
|
83
|
+
return float(axis_coords[index].item())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _window_is_uniform(cells: np.ndarray) -> bool:
|
|
87
|
+
cells = np.asarray(cells, dtype=np.float64)
|
|
88
|
+
d_min = float(cells.min())
|
|
89
|
+
d_max = float(cells.max())
|
|
90
|
+
return (d_max - d_min) <= _SOFT_PLANE_UNIFORM_RTOL * max(d_max, 1e-300)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def soft_plane_wave_region_spacing(scene, *, injection_axis: str, plane_index: int, direction_sign: int) -> dict:
|
|
94
|
+
"""Per-axis primal spacing local to the soft plane-wave launch footprint.
|
|
95
|
+
|
|
96
|
+
The soft plane-wave numerical-dispersion phase correction solves the discrete
|
|
97
|
+
dispersion relation with one effective spacing per axis. On a grid whose
|
|
98
|
+
interior is uniform along an axis (to ``_SOFT_PLANE_UNIFORM_RTOL``) that axis
|
|
99
|
+
returns its exact minimum cell spacing, bit-for-bit the global-minimum value
|
|
100
|
+
the correction used before, so uniform grids are unchanged.
|
|
101
|
+
|
|
102
|
+
On a graded axis the global minimum spacing can belong to a cell far from the
|
|
103
|
+
source, which mis-tunes the injected wavefront's phase velocity. Instead, the
|
|
104
|
+
injection axis returns the spacing of the cell the wavefront is launched into
|
|
105
|
+
(the Yee E/H half-cell offset that governs the one-way phase match lives
|
|
106
|
+
there), and each tangential axis returns the mean spacing over the physical
|
|
107
|
+
aperture (the best single scalar for the oblique-incidence phase across the
|
|
108
|
+
plane).
|
|
109
|
+
"""
|
|
110
|
+
scene = prepare_scene(scene)
|
|
111
|
+
primal = {
|
|
112
|
+
"x": np.asarray(scene.dx_primal64, dtype=np.float64),
|
|
113
|
+
"y": np.asarray(scene.dy_primal64, dtype=np.float64),
|
|
114
|
+
"z": np.asarray(scene.dz_primal64, dtype=np.float64),
|
|
115
|
+
}
|
|
116
|
+
deltas = {}
|
|
117
|
+
for axis in "xyz":
|
|
118
|
+
cells = primal[axis]
|
|
119
|
+
lo, hi = physical_interior_indices(scene, axis)
|
|
120
|
+
interior = cells[lo:hi] if hi > lo else cells
|
|
121
|
+
if _window_is_uniform(interior):
|
|
122
|
+
# Uniform interior: exact spacing, bit-for-bit the previous value.
|
|
123
|
+
deltas[axis] = float(interior.min())
|
|
124
|
+
elif axis == injection_axis:
|
|
125
|
+
# Cell straddled by the launch plane on the propagation side.
|
|
126
|
+
cell_index = plane_index if direction_sign > 0 else plane_index - 1
|
|
127
|
+
cell_index = min(max(int(cell_index), 0), len(cells) - 1)
|
|
128
|
+
deltas[axis] = float(cells[cell_index])
|
|
129
|
+
else:
|
|
130
|
+
deltas[axis] = float(interior.mean())
|
|
131
|
+
return deltas
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def plane_center(scene, axis: str, plane_coordinate: float, *, device, dtype):
|
|
135
|
+
scene = prepare_scene(scene)
|
|
136
|
+
bounds = scene.domain.domain_range
|
|
137
|
+
center = torch.tensor(
|
|
138
|
+
[
|
|
139
|
+
0.5 * (bounds[0] + bounds[1]),
|
|
140
|
+
0.5 * (bounds[2] + bounds[3]),
|
|
141
|
+
0.5 * (bounds[4] + bounds[5]),
|
|
142
|
+
],
|
|
143
|
+
device=device,
|
|
144
|
+
dtype=dtype,
|
|
145
|
+
)
|
|
146
|
+
center[_AXIS_TO_INDEX[axis]] = float(plane_coordinate)
|
|
147
|
+
return center
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def plane_wave_profile(positions, *, direction, reference_point, propagation_speed):
|
|
151
|
+
direction_tensor = positions.new_tensor(direction)
|
|
152
|
+
reference_tensor = (
|
|
153
|
+
reference_point.to(device=positions.device, dtype=positions.dtype)
|
|
154
|
+
if isinstance(reference_point, torch.Tensor)
|
|
155
|
+
else positions.new_tensor(reference_point)
|
|
156
|
+
)
|
|
157
|
+
delay = torch.sum((positions - reference_tensor) * direction_tensor, dim=-1) / float(propagation_speed)
|
|
158
|
+
amplitude = torch.ones_like(delay)
|
|
159
|
+
return amplitude, delay
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _astigmatic_curvature_delay(transverse, z, z_rayleigh, propagation_speed):
|
|
163
|
+
radius_of_curvature = torch.where(
|
|
164
|
+
torch.abs(z) > 1e-9,
|
|
165
|
+
z * (1.0 + (z_rayleigh / torch.clamp(torch.abs(z), min=1e-30)).square()),
|
|
166
|
+
torch.full_like(z, float("inf")),
|
|
167
|
+
)
|
|
168
|
+
return torch.where(
|
|
169
|
+
torch.isfinite(radius_of_curvature),
|
|
170
|
+
transverse.square() / (2.0 * radius_of_curvature * float(propagation_speed)),
|
|
171
|
+
torch.zeros_like(z),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _beam_profile_core(
|
|
176
|
+
positions,
|
|
177
|
+
*,
|
|
178
|
+
direction,
|
|
179
|
+
polarization,
|
|
180
|
+
beam_waist_u,
|
|
181
|
+
beam_waist_v,
|
|
182
|
+
focus,
|
|
183
|
+
focus_offset_u,
|
|
184
|
+
focus_offset_v,
|
|
185
|
+
frequency,
|
|
186
|
+
propagation_speed,
|
|
187
|
+
):
|
|
188
|
+
direction_tensor = positions.new_tensor(direction)
|
|
189
|
+
polarization_tensor = positions.new_tensor(polarization)
|
|
190
|
+
binormal_tensor = torch.cross(direction_tensor, polarization_tensor, dim=0)
|
|
191
|
+
focus_tensor = positions.new_tensor(focus)
|
|
192
|
+
|
|
193
|
+
rel = positions - focus_tensor
|
|
194
|
+
longitudinal = torch.sum(rel * direction_tensor, dim=-1)
|
|
195
|
+
transverse_u = torch.sum(rel * polarization_tensor, dim=-1)
|
|
196
|
+
transverse_v = torch.sum(rel * binormal_tensor, dim=-1)
|
|
197
|
+
|
|
198
|
+
# Per-axis longitudinal coordinate measured from each transverse-axis waist plane.
|
|
199
|
+
z_u = longitudinal - float(focus_offset_u)
|
|
200
|
+
z_v = longitudinal - float(focus_offset_v)
|
|
201
|
+
|
|
202
|
+
wavelength = float(propagation_speed) / float(frequency)
|
|
203
|
+
z_rayleigh_u = math.pi * float(beam_waist_u) ** 2 / max(wavelength, 1e-30)
|
|
204
|
+
z_rayleigh_v = math.pi * float(beam_waist_v) ** 2 / max(wavelength, 1e-30)
|
|
205
|
+
|
|
206
|
+
beam_radius_u = float(beam_waist_u) * torch.sqrt(1.0 + (z_u / max(z_rayleigh_u, 1e-30)).square())
|
|
207
|
+
beam_radius_v = float(beam_waist_v) * torch.sqrt(1.0 + (z_v / max(z_rayleigh_v, 1e-30)).square())
|
|
208
|
+
|
|
209
|
+
amplitude = (
|
|
210
|
+
torch.sqrt(float(beam_waist_u) / beam_radius_u)
|
|
211
|
+
* torch.sqrt(float(beam_waist_v) / beam_radius_v)
|
|
212
|
+
* torch.exp(
|
|
213
|
+
-transverse_u.square() / beam_radius_u.square()
|
|
214
|
+
- transverse_v.square() / beam_radius_v.square()
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
curvature_delay = _astigmatic_curvature_delay(
|
|
219
|
+
transverse_u, z_u, z_rayleigh_u, propagation_speed
|
|
220
|
+
) + _astigmatic_curvature_delay(transverse_v, z_v, z_rayleigh_v, propagation_speed)
|
|
221
|
+
gouy_phase = 0.5 * (
|
|
222
|
+
torch.atan(z_u / max(z_rayleigh_u, 1e-30)) + torch.atan(z_v / max(z_rayleigh_v, 1e-30))
|
|
223
|
+
)
|
|
224
|
+
gouy_delay = gouy_phase / (2.0 * math.pi * float(frequency))
|
|
225
|
+
delay = longitudinal / float(propagation_speed) + curvature_delay - gouy_delay
|
|
226
|
+
return amplitude, delay
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def gaussian_beam_profile(
|
|
230
|
+
positions,
|
|
231
|
+
*,
|
|
232
|
+
direction,
|
|
233
|
+
polarization,
|
|
234
|
+
beam_waist,
|
|
235
|
+
focus,
|
|
236
|
+
frequency,
|
|
237
|
+
propagation_speed,
|
|
238
|
+
):
|
|
239
|
+
return _beam_profile_core(
|
|
240
|
+
positions,
|
|
241
|
+
direction=direction,
|
|
242
|
+
polarization=polarization,
|
|
243
|
+
beam_waist_u=beam_waist,
|
|
244
|
+
beam_waist_v=beam_waist,
|
|
245
|
+
focus=focus,
|
|
246
|
+
focus_offset_u=0.0,
|
|
247
|
+
focus_offset_v=0.0,
|
|
248
|
+
frequency=frequency,
|
|
249
|
+
propagation_speed=propagation_speed,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def astigmatic_gaussian_beam_profile(
|
|
254
|
+
positions,
|
|
255
|
+
*,
|
|
256
|
+
direction,
|
|
257
|
+
polarization,
|
|
258
|
+
beam_waist,
|
|
259
|
+
focus,
|
|
260
|
+
focus_offsets,
|
|
261
|
+
frequency,
|
|
262
|
+
propagation_speed,
|
|
263
|
+
):
|
|
264
|
+
beam_waist_u, beam_waist_v = beam_waist
|
|
265
|
+
focus_offset_u, focus_offset_v = focus_offsets
|
|
266
|
+
return _beam_profile_core(
|
|
267
|
+
positions,
|
|
268
|
+
direction=direction,
|
|
269
|
+
polarization=polarization,
|
|
270
|
+
beam_waist_u=beam_waist_u,
|
|
271
|
+
beam_waist_v=beam_waist_v,
|
|
272
|
+
focus=focus,
|
|
273
|
+
focus_offset_u=focus_offset_u,
|
|
274
|
+
focus_offset_v=focus_offset_v,
|
|
275
|
+
frequency=frequency,
|
|
276
|
+
propagation_speed=propagation_speed,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def beam_profile_from_source(
|
|
281
|
+
positions,
|
|
282
|
+
source,
|
|
283
|
+
*,
|
|
284
|
+
frequency,
|
|
285
|
+
propagation_speed,
|
|
286
|
+
polarization=None,
|
|
287
|
+
):
|
|
288
|
+
polarization = source["polarization"] if polarization is None else polarization
|
|
289
|
+
if source["kind"] == "astigmatic_gaussian_beam":
|
|
290
|
+
return astigmatic_gaussian_beam_profile(
|
|
291
|
+
positions,
|
|
292
|
+
direction=source["direction"],
|
|
293
|
+
polarization=polarization,
|
|
294
|
+
beam_waist=(source["beam_waist_u"], source["beam_waist_v"]),
|
|
295
|
+
focus=source["focus"],
|
|
296
|
+
focus_offsets=(source["focus_u"], source["focus_v"]),
|
|
297
|
+
frequency=frequency,
|
|
298
|
+
propagation_speed=propagation_speed,
|
|
299
|
+
)
|
|
300
|
+
return gaussian_beam_profile(
|
|
301
|
+
positions,
|
|
302
|
+
direction=source["direction"],
|
|
303
|
+
polarization=polarization,
|
|
304
|
+
beam_waist=source["beam_waist"],
|
|
305
|
+
focus=source["focus"],
|
|
306
|
+
frequency=frequency,
|
|
307
|
+
propagation_speed=propagation_speed,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class AuxiliaryGrid1D:
|
|
312
|
+
def __init__(
|
|
313
|
+
self,
|
|
314
|
+
*,
|
|
315
|
+
s_min,
|
|
316
|
+
s_max,
|
|
317
|
+
ds,
|
|
318
|
+
dt,
|
|
319
|
+
wave_speed,
|
|
320
|
+
impedance,
|
|
321
|
+
source_time,
|
|
322
|
+
device,
|
|
323
|
+
dtype,
|
|
324
|
+
absorber_cells=20,
|
|
325
|
+
source_buffer_cells=6,
|
|
326
|
+
fdtd_module=None,
|
|
327
|
+
):
|
|
328
|
+
self.s_min = float(s_min)
|
|
329
|
+
self.s_max = float(s_max)
|
|
330
|
+
self.ds = float(ds)
|
|
331
|
+
self.dt = float(dt)
|
|
332
|
+
self.wave_speed = float(wave_speed)
|
|
333
|
+
self.impedance = float(impedance)
|
|
334
|
+
self.source_time = source_time
|
|
335
|
+
self.device = torch.device(device)
|
|
336
|
+
self.dtype = dtype
|
|
337
|
+
self.absorber_cells = max(int(absorber_cells), 0)
|
|
338
|
+
self.source_buffer_cells = max(int(source_buffer_cells), 0)
|
|
339
|
+
self.fdtd_module = fdtd_module
|
|
340
|
+
|
|
341
|
+
if self.ds <= 0.0:
|
|
342
|
+
raise ValueError("AuxiliaryGrid1D ds must be > 0.")
|
|
343
|
+
if self.dt <= 0.0:
|
|
344
|
+
raise ValueError("AuxiliaryGrid1D dt must be > 0.")
|
|
345
|
+
if self.wave_speed <= 0.0:
|
|
346
|
+
raise ValueError("AuxiliaryGrid1D wave_speed must be > 0.")
|
|
347
|
+
if self.s_max <= self.s_min:
|
|
348
|
+
raise ValueError("AuxiliaryGrid1D requires s_max > s_min.")
|
|
349
|
+
|
|
350
|
+
electric_cells = max(int(math.ceil((self.s_max - self.s_min) / self.ds)) + 1, 4)
|
|
351
|
+
self.electric = torch.zeros(electric_cells, device=self.device, dtype=dtype)
|
|
352
|
+
self.magnetic = torch.zeros(electric_cells - 1, device=self.device, dtype=dtype)
|
|
353
|
+
self.source_index = 0
|
|
354
|
+
|
|
355
|
+
self.eps = 1.0 / (self.impedance * self.wave_speed)
|
|
356
|
+
self.mu = self.impedance / self.wave_speed
|
|
357
|
+
self._build_loss_profiles()
|
|
358
|
+
self.time_step = 0
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def electric_time(self):
|
|
362
|
+
return self.time_step * self.dt
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def magnetic_time(self):
|
|
366
|
+
return (self.time_step - 0.5) * self.dt
|
|
367
|
+
|
|
368
|
+
def _can_use_compiled_auxiliary(self) -> bool:
|
|
369
|
+
return self.device.type == "cuda" and self.dtype == torch.float32
|
|
370
|
+
|
|
371
|
+
def _resolve_fdtd_module(self):
|
|
372
|
+
if self.fdtd_module is not None:
|
|
373
|
+
return self.fdtd_module
|
|
374
|
+
if not self._can_use_compiled_auxiliary():
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
from ..cuda.backend import get_native_fdtd_module
|
|
378
|
+
|
|
379
|
+
self.fdtd_module = get_native_fdtd_module()
|
|
380
|
+
return self.fdtd_module
|
|
381
|
+
|
|
382
|
+
def _build_loss_profiles(self):
|
|
383
|
+
sigma_e = torch.zeros_like(self.electric)
|
|
384
|
+
sigma_h = torch.zeros_like(self.magnetic)
|
|
385
|
+
edge_cells_e = min(self.absorber_cells, max(self.electric.numel() - 1, 0))
|
|
386
|
+
edge_cells_h = min(self.absorber_cells, max(self.magnetic.numel(), 0))
|
|
387
|
+
if edge_cells_e > 0:
|
|
388
|
+
max_sigma = 0.35
|
|
389
|
+
ramp_e = ((torch.arange(edge_cells_e, device=self.device, dtype=self.dtype) + 1.0) / edge_cells_e) ** 2
|
|
390
|
+
sigma_e[-edge_cells_e:] = max_sigma * ramp_e
|
|
391
|
+
if edge_cells_h > 0:
|
|
392
|
+
max_sigma = 0.35
|
|
393
|
+
ramp_h = ((torch.arange(edge_cells_h, device=self.device, dtype=self.dtype) + 0.5) / edge_cells_h) ** 2
|
|
394
|
+
sigma_h[-edge_cells_h:] = max_sigma * ramp_h
|
|
395
|
+
|
|
396
|
+
sigma_factor_e = 0.5 * self.dt * sigma_e / max(self.eps, 1e-30)
|
|
397
|
+
sigma_factor_h = 0.5 * self.dt * sigma_h / max(self.mu, 1e-30)
|
|
398
|
+
self.electric_decay = (1.0 - sigma_factor_e) / (1.0 + sigma_factor_e)
|
|
399
|
+
self.electric_curl = (self.dt / (self.eps * self.ds)) / (1.0 + sigma_factor_e)
|
|
400
|
+
self.magnetic_decay = (1.0 - sigma_factor_h) / (1.0 + sigma_factor_h)
|
|
401
|
+
self.magnetic_curl = (self.dt / (self.mu * self.ds)) / (1.0 + sigma_factor_h)
|
|
402
|
+
|
|
403
|
+
def _interpolate(self, field, *, origin, positions, out=None):
|
|
404
|
+
positions_tensor = torch.as_tensor(positions, device=self.device, dtype=self.dtype)
|
|
405
|
+
coord = torch.clamp((positions_tensor - float(origin)) / self.ds, min=0.0, max=field.numel() - 1.0)
|
|
406
|
+
lower = torch.floor(coord).to(torch.int64)
|
|
407
|
+
upper = torch.clamp(lower + 1, max=field.numel() - 1)
|
|
408
|
+
frac = coord - lower.to(self.dtype)
|
|
409
|
+
if out is None:
|
|
410
|
+
out = torch.empty_like(positions_tensor)
|
|
411
|
+
out.copy_(field[lower])
|
|
412
|
+
out.lerp_(field[upper], frac)
|
|
413
|
+
return out
|
|
414
|
+
|
|
415
|
+
def sample_e(self, positions, out=None):
|
|
416
|
+
return self._interpolate(self.electric, origin=self.s_min, positions=positions, out=out)
|
|
417
|
+
|
|
418
|
+
def sample_h(self, positions, out=None):
|
|
419
|
+
return self._interpolate(self.magnetic, origin=self.s_min + 0.5 * self.ds, positions=positions, out=out)
|
|
420
|
+
|
|
421
|
+
def _apply_source(self):
|
|
422
|
+
self.electric[self.source_index] = evaluate_source_time(self.source_time, self.electric_time)
|
|
423
|
+
|
|
424
|
+
def _advance_magnetic_compiled(self):
|
|
425
|
+
self._resolve_fdtd_module().updateAuxiliaryMagnetic1D(
|
|
426
|
+
Magnetic=self.magnetic,
|
|
427
|
+
Electric=self.electric,
|
|
428
|
+
MagneticDecay=self.magnetic_decay,
|
|
429
|
+
MagneticCurl=self.magnetic_curl,
|
|
430
|
+
).launchRaw()
|
|
431
|
+
|
|
432
|
+
def _advance_electric_compiled(self):
|
|
433
|
+
source_value = float(evaluate_source_time(self.source_time, self.electric_time))
|
|
434
|
+
self._resolve_fdtd_module().updateAuxiliaryElectric1D(
|
|
435
|
+
Electric=self.electric,
|
|
436
|
+
Magnetic=self.magnetic,
|
|
437
|
+
ElectricDecay=self.electric_decay,
|
|
438
|
+
ElectricCurl=self.electric_curl,
|
|
439
|
+
sourceIndex=int(self.source_index),
|
|
440
|
+
sourceValue=source_value,
|
|
441
|
+
).launchRaw()
|
|
442
|
+
|
|
443
|
+
def advance_magnetic(self):
|
|
444
|
+
if self._can_use_compiled_auxiliary():
|
|
445
|
+
self._advance_magnetic_compiled()
|
|
446
|
+
return
|
|
447
|
+
if self.magnetic.numel() > 0:
|
|
448
|
+
curl_e = self.electric[1:] - self.electric[:-1]
|
|
449
|
+
self.magnetic = self.magnetic_decay * self.magnetic - self.magnetic_curl * curl_e
|
|
450
|
+
|
|
451
|
+
def advance_electric(self):
|
|
452
|
+
if self._can_use_compiled_auxiliary():
|
|
453
|
+
self._advance_electric_compiled()
|
|
454
|
+
self.time_step += 1
|
|
455
|
+
return
|
|
456
|
+
if self.electric.numel() > 2:
|
|
457
|
+
curl_h = self.magnetic[1:] - self.magnetic[:-1]
|
|
458
|
+
self.electric[1:-1] = self.electric_decay[1:-1] * self.electric[1:-1] - self.electric_curl[1:-1] * curl_h
|
|
459
|
+
|
|
460
|
+
self._apply_source()
|
|
461
|
+
self.electric[-1] = 0.0
|
|
462
|
+
self.time_step += 1
|
|
463
|
+
|
|
464
|
+
def advance(self):
|
|
465
|
+
self.advance_magnetic()
|
|
466
|
+
self.advance_electric()
|
|
467
|
+
|
|
468
|
+
def step(self):
|
|
469
|
+
self.advance()
|