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,1987 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import torch
|
|
7
|
+
from tqdm import tqdm
|
|
8
|
+
|
|
9
|
+
from ...visualization import visualize_slice
|
|
10
|
+
from ..boundary import (
|
|
11
|
+
BOUNDARY_BLOCH,
|
|
12
|
+
BOUNDARY_PEC,
|
|
13
|
+
BOUNDARY_PERIODIC,
|
|
14
|
+
BOUNDARY_PML,
|
|
15
|
+
has_complex_fields,
|
|
16
|
+
initialize_boundary_state,
|
|
17
|
+
)
|
|
18
|
+
from ..excitation import (
|
|
19
|
+
advance_tfsf_auxiliary_electric,
|
|
20
|
+
advance_tfsf_auxiliary_magnetic,
|
|
21
|
+
apply_tfsf_e_correction,
|
|
22
|
+
apply_tfsf_h_correction,
|
|
23
|
+
inject_electric_surface_source_terms,
|
|
24
|
+
inject_magnetic_surface_source_terms,
|
|
25
|
+
initialize_source_terms,
|
|
26
|
+
initialize_tfsf_state,
|
|
27
|
+
tfsf_incident_is_gpu_driven,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def iter_cpml_memory_regions(solver, attr_name):
|
|
32
|
+
layout = getattr(solver, "_cpml_memory_layouts", {}).get(attr_name)
|
|
33
|
+
if layout is None:
|
|
34
|
+
return
|
|
35
|
+
axis = int(layout["axis"])
|
|
36
|
+
tensor = getattr(solver, attr_name)
|
|
37
|
+
for region in layout["regions"]:
|
|
38
|
+
length = int(region["length"])
|
|
39
|
+
if length <= 0:
|
|
40
|
+
continue
|
|
41
|
+
local_start = int(region["local_start"])
|
|
42
|
+
global_start = int(region["global_start"])
|
|
43
|
+
psi_region = tensor.narrow(axis, local_start, length)
|
|
44
|
+
offsets = [0, 0, 0]
|
|
45
|
+
offsets[axis] = global_start
|
|
46
|
+
yield {
|
|
47
|
+
"axis": axis,
|
|
48
|
+
"length": length,
|
|
49
|
+
"offsets": tuple(offsets),
|
|
50
|
+
"psi": psi_region,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def cpml_layout_params(solver, attr_name):
|
|
55
|
+
cached = solver._cpml_layout_cache.get(attr_name)
|
|
56
|
+
if cached is not None:
|
|
57
|
+
return cached
|
|
58
|
+
layout = getattr(solver, "_cpml_memory_layouts", {}).get(attr_name)
|
|
59
|
+
if layout is None:
|
|
60
|
+
result = (0, 0, 0)
|
|
61
|
+
else:
|
|
62
|
+
low = next((region for region in layout["regions"] if region["side"] == "low"), None)
|
|
63
|
+
high = next((region for region in layout["regions"] if region["side"] == "high"), None)
|
|
64
|
+
low_length = 0 if low is None else int(low["length"])
|
|
65
|
+
high_start = 0 if high is None else int(high["global_start"])
|
|
66
|
+
high_length = 0 if high is None else int(high["length"])
|
|
67
|
+
result = (low_length, high_start, high_length)
|
|
68
|
+
solver._cpml_layout_cache[attr_name] = result
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _cpml_memory_attr(attr_name, *, imag=False):
|
|
73
|
+
return f"{attr_name}_imag" if imag else attr_name
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _cpml_memory_tensor(solver, attr_name, *, imag=False):
|
|
77
|
+
return getattr(solver, _cpml_memory_attr(attr_name, imag=imag))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def update_magnetic_fields_cpml_dense(solver, hx, hy, hz, ex, ey, ez, *, imag=False):
|
|
81
|
+
solver.fdtd_module.updateMagneticFieldHx3D(
|
|
82
|
+
Hx=hx,
|
|
83
|
+
Ey=ey,
|
|
84
|
+
Ez=ez,
|
|
85
|
+
HxDecay=solver.chx_decay,
|
|
86
|
+
HxCurl=solver.chx_curl,
|
|
87
|
+
PsiHxY=_cpml_memory_tensor(solver, "psi_hx_y", imag=imag),
|
|
88
|
+
PsiHxZ=_cpml_memory_tensor(solver, "psi_hx_z", imag=imag),
|
|
89
|
+
InvKappaHxY=solver.cpml_inv_kappa_h_y,
|
|
90
|
+
ByHxY=solver.cpml_b_h_y,
|
|
91
|
+
CyHxY=solver.cpml_c_h_y,
|
|
92
|
+
InvKappaHxZ=solver.cpml_inv_kappa_h_z,
|
|
93
|
+
ByHxZ=solver.cpml_b_h_z,
|
|
94
|
+
CyHxZ=solver.cpml_c_h_z,
|
|
95
|
+
invDy=solver.inv_dy_h,
|
|
96
|
+
invDz=solver.inv_dz_h,
|
|
97
|
+
).launchRaw()
|
|
98
|
+
solver.fdtd_module.updateMagneticFieldHy3D(
|
|
99
|
+
Hy=hy,
|
|
100
|
+
Ex=ex,
|
|
101
|
+
Ez=ez,
|
|
102
|
+
HyDecay=solver.chy_decay,
|
|
103
|
+
HyCurl=solver.chy_curl,
|
|
104
|
+
PsiHyX=_cpml_memory_tensor(solver, "psi_hy_x", imag=imag),
|
|
105
|
+
PsiHyZ=_cpml_memory_tensor(solver, "psi_hy_z", imag=imag),
|
|
106
|
+
InvKappaHyX=solver.cpml_inv_kappa_h_x,
|
|
107
|
+
ByHyX=solver.cpml_b_h_x,
|
|
108
|
+
CyHyX=solver.cpml_c_h_x,
|
|
109
|
+
InvKappaHyZ=solver.cpml_inv_kappa_h_z,
|
|
110
|
+
ByHyZ=solver.cpml_b_h_z,
|
|
111
|
+
CyHyZ=solver.cpml_c_h_z,
|
|
112
|
+
invDx=solver.inv_dx_h,
|
|
113
|
+
invDz=solver.inv_dz_h,
|
|
114
|
+
).launchRaw()
|
|
115
|
+
solver.fdtd_module.updateMagneticFieldHz3D(
|
|
116
|
+
Hz=hz,
|
|
117
|
+
Ex=ex,
|
|
118
|
+
Ey=ey,
|
|
119
|
+
HzDecay=solver.chz_decay,
|
|
120
|
+
HzCurl=solver.chz_curl,
|
|
121
|
+
PsiHzX=_cpml_memory_tensor(solver, "psi_hz_x", imag=imag),
|
|
122
|
+
PsiHzY=_cpml_memory_tensor(solver, "psi_hz_y", imag=imag),
|
|
123
|
+
InvKappaHzX=solver.cpml_inv_kappa_h_x,
|
|
124
|
+
ByHzX=solver.cpml_b_h_x,
|
|
125
|
+
CyHzX=solver.cpml_c_h_x,
|
|
126
|
+
InvKappaHzY=solver.cpml_inv_kappa_h_y,
|
|
127
|
+
ByHzY=solver.cpml_b_h_y,
|
|
128
|
+
CyHzY=solver.cpml_c_h_y,
|
|
129
|
+
invDx=solver.inv_dx_h,
|
|
130
|
+
invDy=solver.inv_dy_h,
|
|
131
|
+
).launchRaw()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def update_magnetic_fields_cpml_compressed(solver, hx, hy, hz, ex, ey, ez, *, imag=False):
|
|
135
|
+
psi_hx_y_attr = _cpml_memory_attr("psi_hx_y", imag=imag)
|
|
136
|
+
psi_hx_z_attr = _cpml_memory_attr("psi_hx_z", imag=imag)
|
|
137
|
+
psi_hy_x_attr = _cpml_memory_attr("psi_hy_x", imag=imag)
|
|
138
|
+
psi_hy_z_attr = _cpml_memory_attr("psi_hy_z", imag=imag)
|
|
139
|
+
psi_hz_x_attr = _cpml_memory_attr("psi_hz_x", imag=imag)
|
|
140
|
+
psi_hz_y_attr = _cpml_memory_attr("psi_hz_y", imag=imag)
|
|
141
|
+
psi_hx_y_low, psi_hx_y_high_start, psi_hx_y_high = cpml_layout_params(solver, psi_hx_y_attr)
|
|
142
|
+
psi_hx_z_low, psi_hx_z_high_start, psi_hx_z_high = cpml_layout_params(solver, psi_hx_z_attr)
|
|
143
|
+
psi_hy_x_low, psi_hy_x_high_start, psi_hy_x_high = cpml_layout_params(solver, psi_hy_x_attr)
|
|
144
|
+
psi_hy_z_low, psi_hy_z_high_start, psi_hy_z_high = cpml_layout_params(solver, psi_hy_z_attr)
|
|
145
|
+
psi_hz_x_low, psi_hz_x_high_start, psi_hz_x_high = cpml_layout_params(solver, psi_hz_x_attr)
|
|
146
|
+
psi_hz_y_low, psi_hz_y_high_start, psi_hz_y_high = cpml_layout_params(solver, psi_hz_y_attr)
|
|
147
|
+
|
|
148
|
+
solver.fdtd_module.updateMagneticFieldHxCpmlCompressed3D(
|
|
149
|
+
Hx=hx,
|
|
150
|
+
Ey=ey,
|
|
151
|
+
Ez=ez,
|
|
152
|
+
HxDecay=solver.chx_decay,
|
|
153
|
+
HxCurl=solver.chx_curl,
|
|
154
|
+
PsiHxY=_cpml_memory_tensor(solver, "psi_hx_y", imag=imag),
|
|
155
|
+
PsiHxZ=_cpml_memory_tensor(solver, "psi_hx_z", imag=imag),
|
|
156
|
+
InvKappaHxY=solver.cpml_inv_kappa_h_y,
|
|
157
|
+
ByHxY=solver.cpml_b_h_y,
|
|
158
|
+
CyHxY=solver.cpml_c_h_y,
|
|
159
|
+
InvKappaHxZ=solver.cpml_inv_kappa_h_z,
|
|
160
|
+
ByHxZ=solver.cpml_b_h_z,
|
|
161
|
+
CyHxZ=solver.cpml_c_h_z,
|
|
162
|
+
invDy=solver.inv_dy_h,
|
|
163
|
+
invDz=solver.inv_dz_h,
|
|
164
|
+
psiHxYLowLength=psi_hx_y_low,
|
|
165
|
+
psiHxYHighStart=psi_hx_y_high_start,
|
|
166
|
+
psiHxYHighLength=psi_hx_y_high,
|
|
167
|
+
psiHxZLowLength=psi_hx_z_low,
|
|
168
|
+
psiHxZHighStart=psi_hx_z_high_start,
|
|
169
|
+
psiHxZHighLength=psi_hx_z_high,
|
|
170
|
+
uniformDecay=solver._coefficient_uniformity["chx_decay"],
|
|
171
|
+
uniformCurl=solver._coefficient_uniformity["chx_curl"],
|
|
172
|
+
).launchRaw()
|
|
173
|
+
solver.fdtd_module.updateMagneticFieldHyCpmlCompressed3D(
|
|
174
|
+
Hy=hy,
|
|
175
|
+
Ex=ex,
|
|
176
|
+
Ez=ez,
|
|
177
|
+
HyDecay=solver.chy_decay,
|
|
178
|
+
HyCurl=solver.chy_curl,
|
|
179
|
+
PsiHyX=_cpml_memory_tensor(solver, "psi_hy_x", imag=imag),
|
|
180
|
+
PsiHyZ=_cpml_memory_tensor(solver, "psi_hy_z", imag=imag),
|
|
181
|
+
InvKappaHyX=solver.cpml_inv_kappa_h_x,
|
|
182
|
+
ByHyX=solver.cpml_b_h_x,
|
|
183
|
+
CyHyX=solver.cpml_c_h_x,
|
|
184
|
+
InvKappaHyZ=solver.cpml_inv_kappa_h_z,
|
|
185
|
+
ByHyZ=solver.cpml_b_h_z,
|
|
186
|
+
CyHyZ=solver.cpml_c_h_z,
|
|
187
|
+
invDx=solver.inv_dx_h,
|
|
188
|
+
invDz=solver.inv_dz_h,
|
|
189
|
+
psiHyXLowLength=psi_hy_x_low,
|
|
190
|
+
psiHyXHighStart=psi_hy_x_high_start,
|
|
191
|
+
psiHyXHighLength=psi_hy_x_high,
|
|
192
|
+
psiHyZLowLength=psi_hy_z_low,
|
|
193
|
+
psiHyZHighStart=psi_hy_z_high_start,
|
|
194
|
+
psiHyZHighLength=psi_hy_z_high,
|
|
195
|
+
uniformDecay=solver._coefficient_uniformity["chy_decay"],
|
|
196
|
+
uniformCurl=solver._coefficient_uniformity["chy_curl"],
|
|
197
|
+
).launchRaw()
|
|
198
|
+
solver.fdtd_module.updateMagneticFieldHzCpmlCompressed3D(
|
|
199
|
+
Hz=hz,
|
|
200
|
+
Ex=ex,
|
|
201
|
+
Ey=ey,
|
|
202
|
+
HzDecay=solver.chz_decay,
|
|
203
|
+
HzCurl=solver.chz_curl,
|
|
204
|
+
PsiHzX=_cpml_memory_tensor(solver, "psi_hz_x", imag=imag),
|
|
205
|
+
PsiHzY=_cpml_memory_tensor(solver, "psi_hz_y", imag=imag),
|
|
206
|
+
InvKappaHzX=solver.cpml_inv_kappa_h_x,
|
|
207
|
+
ByHzX=solver.cpml_b_h_x,
|
|
208
|
+
CyHzX=solver.cpml_c_h_x,
|
|
209
|
+
InvKappaHzY=solver.cpml_inv_kappa_h_y,
|
|
210
|
+
ByHzY=solver.cpml_b_h_y,
|
|
211
|
+
CyHzY=solver.cpml_c_h_y,
|
|
212
|
+
invDx=solver.inv_dx_h,
|
|
213
|
+
invDy=solver.inv_dy_h,
|
|
214
|
+
psiHzXLowLength=psi_hz_x_low,
|
|
215
|
+
psiHzXHighStart=psi_hz_x_high_start,
|
|
216
|
+
psiHzXHighLength=psi_hz_x_high,
|
|
217
|
+
psiHzYLowLength=psi_hz_y_low,
|
|
218
|
+
psiHzYHighStart=psi_hz_y_high_start,
|
|
219
|
+
psiHzYHighLength=psi_hz_y_high,
|
|
220
|
+
uniformDecay=solver._coefficient_uniformity["chz_decay"],
|
|
221
|
+
uniformCurl=solver._coefficient_uniformity["chz_curl"],
|
|
222
|
+
).launchRaw()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def update_magnetic_fields_cpml(solver, hx, hy, hz, ex, ey, ez, *, imag=False):
|
|
226
|
+
if solver._cpml_memory_mode == "dense":
|
|
227
|
+
update_magnetic_fields_cpml_dense(solver, hx, hy, hz, ex, ey, ez, imag=imag)
|
|
228
|
+
return
|
|
229
|
+
update_magnetic_fields_cpml_compressed(solver, hx, hy, hz, ex, ey, ez, imag=imag)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def update_magnetic_fields_standard(solver, hx, hy, hz, ex, ey, ez):
|
|
233
|
+
solver.fdtd_module.updateMagneticFieldHxStandard3D(
|
|
234
|
+
Hx=hx,
|
|
235
|
+
Ey=ey,
|
|
236
|
+
Ez=ez,
|
|
237
|
+
HxDecay=solver.chx_decay,
|
|
238
|
+
HxCurl=solver.chx_curl,
|
|
239
|
+
invDy=solver.inv_dy_h,
|
|
240
|
+
invDz=solver.inv_dz_h,
|
|
241
|
+
).launchRaw()
|
|
242
|
+
solver.fdtd_module.updateMagneticFieldHyStandard3D(
|
|
243
|
+
Hy=hy,
|
|
244
|
+
Ex=ex,
|
|
245
|
+
Ez=ez,
|
|
246
|
+
HyDecay=solver.chy_decay,
|
|
247
|
+
HyCurl=solver.chy_curl,
|
|
248
|
+
invDx=solver.inv_dx_h,
|
|
249
|
+
invDz=solver.inv_dz_h,
|
|
250
|
+
).launchRaw()
|
|
251
|
+
solver.fdtd_module.updateMagneticFieldHzStandard3D(
|
|
252
|
+
Hz=hz,
|
|
253
|
+
Ex=ex,
|
|
254
|
+
Ey=ey,
|
|
255
|
+
HzDecay=solver.chz_decay,
|
|
256
|
+
HzCurl=solver.chz_curl,
|
|
257
|
+
invDx=solver.inv_dx_h,
|
|
258
|
+
invDy=solver.inv_dy_h,
|
|
259
|
+
).launchRaw()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def update_magnetic_fields(solver, hx, hy, hz, ex, ey, ez, *, imag=False):
|
|
263
|
+
if solver.uses_cpml:
|
|
264
|
+
update_magnetic_fields_cpml(solver, hx, hy, hz, ex, ey, ez, imag=imag)
|
|
265
|
+
else:
|
|
266
|
+
update_magnetic_fields_standard(solver, hx, hy, hz, ex, ey, ez)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _electric_curl_tensors(solver):
|
|
270
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
271
|
+
return solver.cex_curl_dynamic, solver.cey_curl_dynamic, solver.cez_curl_dynamic
|
|
272
|
+
return solver.cex_curl, solver.cey_curl, solver.cez_curl
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _electric_decay_tensors(solver):
|
|
276
|
+
# The general nonlinear kernel (chi2 / field-dependent conductivity)
|
|
277
|
+
# rewrites the decay coefficients per step; pure-chi3 Kerr keeps the
|
|
278
|
+
# static decay tensors.
|
|
279
|
+
if getattr(solver, "nonlinear_general_enabled", False):
|
|
280
|
+
return solver.cex_decay_dynamic, solver.cey_decay_dynamic, solver.cez_decay_dynamic
|
|
281
|
+
return solver.cex_decay, solver.cey_decay, solver.cez_decay
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def update_electric_fields_cpml_dense(solver, ex, ey, ez, hx, hy, hz):
|
|
285
|
+
ex_curl, ey_curl, ez_curl = _electric_curl_tensors(solver)
|
|
286
|
+
ex_decay, ey_decay, ez_decay = _electric_decay_tensors(solver)
|
|
287
|
+
solver.fdtd_module.updateElectricFieldExCpml3D(
|
|
288
|
+
Ex=ex,
|
|
289
|
+
Hy=hy,
|
|
290
|
+
Hz=hz,
|
|
291
|
+
ExDecay=ex_decay,
|
|
292
|
+
ExCurl=ex_curl,
|
|
293
|
+
PsiExY=solver.psi_ex_y,
|
|
294
|
+
PsiExZ=solver.psi_ex_z,
|
|
295
|
+
InvKappaExY=solver.cpml_inv_kappa_e_y,
|
|
296
|
+
BExY=solver.cpml_b_e_y,
|
|
297
|
+
CExY=solver.cpml_c_e_y,
|
|
298
|
+
InvKappaExZ=solver.cpml_inv_kappa_e_z,
|
|
299
|
+
BExZ=solver.cpml_b_e_z,
|
|
300
|
+
CExZ=solver.cpml_c_e_z,
|
|
301
|
+
invDy=solver.inv_dy_e,
|
|
302
|
+
invDz=solver.inv_dz_e,
|
|
303
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
304
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
305
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
306
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
307
|
+
).launchRaw()
|
|
308
|
+
solver.fdtd_module.updateElectricFieldEyCpml3D(
|
|
309
|
+
Ey=ey,
|
|
310
|
+
Hx=hx,
|
|
311
|
+
Hz=hz,
|
|
312
|
+
EyDecay=ey_decay,
|
|
313
|
+
EyCurl=ey_curl,
|
|
314
|
+
PsiEyX=solver.psi_ey_x,
|
|
315
|
+
PsiEyZ=solver.psi_ey_z,
|
|
316
|
+
InvKappaEyX=solver.cpml_inv_kappa_e_x,
|
|
317
|
+
BEyX=solver.cpml_b_e_x,
|
|
318
|
+
CEyX=solver.cpml_c_e_x,
|
|
319
|
+
InvKappaEyZ=solver.cpml_inv_kappa_e_z,
|
|
320
|
+
BEyZ=solver.cpml_b_e_z,
|
|
321
|
+
CEyZ=solver.cpml_c_e_z,
|
|
322
|
+
invDx=solver.inv_dx_e,
|
|
323
|
+
invDz=solver.inv_dz_e,
|
|
324
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
325
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
326
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
327
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
328
|
+
).launchRaw()
|
|
329
|
+
solver.fdtd_module.updateElectricFieldEzCpml3D(
|
|
330
|
+
Ez=ez,
|
|
331
|
+
Hx=hx,
|
|
332
|
+
Hy=hy,
|
|
333
|
+
EzDecay=ez_decay,
|
|
334
|
+
EzCurl=ez_curl,
|
|
335
|
+
PsiEzX=solver.psi_ez_x,
|
|
336
|
+
PsiEzY=solver.psi_ez_y,
|
|
337
|
+
InvKappaEzX=solver.cpml_inv_kappa_e_x,
|
|
338
|
+
BEzX=solver.cpml_b_e_x,
|
|
339
|
+
CEzX=solver.cpml_c_e_x,
|
|
340
|
+
InvKappaEzY=solver.cpml_inv_kappa_e_y,
|
|
341
|
+
BEzY=solver.cpml_b_e_y,
|
|
342
|
+
CEzY=solver.cpml_c_e_y,
|
|
343
|
+
invDx=solver.inv_dx_e,
|
|
344
|
+
invDy=solver.inv_dy_e,
|
|
345
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
346
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
347
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
348
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
349
|
+
).launchRaw()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def update_electric_fields_cpml_compressed(solver, ex, ey, ez, hx, hy, hz):
|
|
353
|
+
psi_ex_y_low, psi_ex_y_high_start, psi_ex_y_high = cpml_layout_params(solver, "psi_ex_y")
|
|
354
|
+
psi_ex_z_low, psi_ex_z_high_start, psi_ex_z_high = cpml_layout_params(solver, "psi_ex_z")
|
|
355
|
+
psi_ey_x_low, psi_ey_x_high_start, psi_ey_x_high = cpml_layout_params(solver, "psi_ey_x")
|
|
356
|
+
psi_ey_z_low, psi_ey_z_high_start, psi_ey_z_high = cpml_layout_params(solver, "psi_ey_z")
|
|
357
|
+
psi_ez_x_low, psi_ez_x_high_start, psi_ez_x_high = cpml_layout_params(solver, "psi_ez_x")
|
|
358
|
+
psi_ez_y_low, psi_ez_y_high_start, psi_ez_y_high = cpml_layout_params(solver, "psi_ez_y")
|
|
359
|
+
ex_curl, ey_curl, ez_curl = _electric_curl_tensors(solver)
|
|
360
|
+
ex_decay, ey_decay, ez_decay = _electric_decay_tensors(solver)
|
|
361
|
+
|
|
362
|
+
solver.fdtd_module.updateElectricFieldExCpmlCompressed3D(
|
|
363
|
+
Ex=ex,
|
|
364
|
+
Hy=hy,
|
|
365
|
+
Hz=hz,
|
|
366
|
+
ExDecay=ex_decay,
|
|
367
|
+
ExCurl=ex_curl,
|
|
368
|
+
PsiExY=solver.psi_ex_y,
|
|
369
|
+
PsiExZ=solver.psi_ex_z,
|
|
370
|
+
InvKappaExY=solver.cpml_inv_kappa_e_y,
|
|
371
|
+
BExY=solver.cpml_b_e_y,
|
|
372
|
+
CExY=solver.cpml_c_e_y,
|
|
373
|
+
InvKappaExZ=solver.cpml_inv_kappa_e_z,
|
|
374
|
+
BExZ=solver.cpml_b_e_z,
|
|
375
|
+
CExZ=solver.cpml_c_e_z,
|
|
376
|
+
invDy=solver.inv_dy_e,
|
|
377
|
+
invDz=solver.inv_dz_e,
|
|
378
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
379
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
380
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
381
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
382
|
+
psiExYLowLength=psi_ex_y_low,
|
|
383
|
+
psiExYHighStart=psi_ex_y_high_start,
|
|
384
|
+
psiExYHighLength=psi_ex_y_high,
|
|
385
|
+
psiExZLowLength=psi_ex_z_low,
|
|
386
|
+
psiExZHighStart=psi_ex_z_high_start,
|
|
387
|
+
psiExZHighLength=psi_ex_z_high,
|
|
388
|
+
uniformDecay=None if ex_decay is not solver.cex_decay else solver._coefficient_uniformity["cex_decay"],
|
|
389
|
+
uniformCurl=None if ex_curl is not solver.cex_curl else solver._coefficient_uniformity["cex_curl"],
|
|
390
|
+
).launchRaw()
|
|
391
|
+
solver.fdtd_module.updateElectricFieldEyCpmlCompressed3D(
|
|
392
|
+
Ey=ey,
|
|
393
|
+
Hx=hx,
|
|
394
|
+
Hz=hz,
|
|
395
|
+
EyDecay=ey_decay,
|
|
396
|
+
EyCurl=ey_curl,
|
|
397
|
+
PsiEyX=solver.psi_ey_x,
|
|
398
|
+
PsiEyZ=solver.psi_ey_z,
|
|
399
|
+
InvKappaEyX=solver.cpml_inv_kappa_e_x,
|
|
400
|
+
BEyX=solver.cpml_b_e_x,
|
|
401
|
+
CEyX=solver.cpml_c_e_x,
|
|
402
|
+
InvKappaEyZ=solver.cpml_inv_kappa_e_z,
|
|
403
|
+
BEyZ=solver.cpml_b_e_z,
|
|
404
|
+
CEyZ=solver.cpml_c_e_z,
|
|
405
|
+
invDx=solver.inv_dx_e,
|
|
406
|
+
invDz=solver.inv_dz_e,
|
|
407
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
408
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
409
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
410
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
411
|
+
psiEyXLowLength=psi_ey_x_low,
|
|
412
|
+
psiEyXHighStart=psi_ey_x_high_start,
|
|
413
|
+
psiEyXHighLength=psi_ey_x_high,
|
|
414
|
+
psiEyZLowLength=psi_ey_z_low,
|
|
415
|
+
psiEyZHighStart=psi_ey_z_high_start,
|
|
416
|
+
psiEyZHighLength=psi_ey_z_high,
|
|
417
|
+
uniformDecay=None if ey_decay is not solver.cey_decay else solver._coefficient_uniformity["cey_decay"],
|
|
418
|
+
uniformCurl=None if ey_curl is not solver.cey_curl else solver._coefficient_uniformity["cey_curl"],
|
|
419
|
+
).launchRaw()
|
|
420
|
+
solver.fdtd_module.updateElectricFieldEzCpmlCompressed3D(
|
|
421
|
+
Ez=ez,
|
|
422
|
+
Hx=hx,
|
|
423
|
+
Hy=hy,
|
|
424
|
+
EzDecay=ez_decay,
|
|
425
|
+
EzCurl=ez_curl,
|
|
426
|
+
PsiEzX=solver.psi_ez_x,
|
|
427
|
+
PsiEzY=solver.psi_ez_y,
|
|
428
|
+
InvKappaEzX=solver.cpml_inv_kappa_e_x,
|
|
429
|
+
BEzX=solver.cpml_b_e_x,
|
|
430
|
+
CEzX=solver.cpml_c_e_x,
|
|
431
|
+
InvKappaEzY=solver.cpml_inv_kappa_e_y,
|
|
432
|
+
BEzY=solver.cpml_b_e_y,
|
|
433
|
+
CEzY=solver.cpml_c_e_y,
|
|
434
|
+
invDx=solver.inv_dx_e,
|
|
435
|
+
invDy=solver.inv_dy_e,
|
|
436
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
437
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
438
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
439
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
440
|
+
psiEzXLowLength=psi_ez_x_low,
|
|
441
|
+
psiEzXHighStart=psi_ez_x_high_start,
|
|
442
|
+
psiEzXHighLength=psi_ez_x_high,
|
|
443
|
+
psiEzYLowLength=psi_ez_y_low,
|
|
444
|
+
psiEzYHighStart=psi_ez_y_high_start,
|
|
445
|
+
psiEzYHighLength=psi_ez_y_high,
|
|
446
|
+
uniformDecay=None if ez_decay is not solver.cez_decay else solver._coefficient_uniformity["cez_decay"],
|
|
447
|
+
uniformCurl=None if ez_curl is not solver.cez_curl else solver._coefficient_uniformity["cez_curl"],
|
|
448
|
+
).launchRaw()
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def update_electric_fields_cpml(solver, ex, ey, ez, hx, hy, hz):
|
|
452
|
+
if solver._cpml_memory_mode == "dense":
|
|
453
|
+
update_electric_fields_cpml_dense(solver, ex, ey, ez, hx, hy, hz)
|
|
454
|
+
return
|
|
455
|
+
update_electric_fields_cpml_compressed(solver, ex, ey, ez, hx, hy, hz)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def update_electric_fields_standard(solver, ex, ey, ez, hx, hy, hz):
|
|
459
|
+
ex_curl, ey_curl, ez_curl = _electric_curl_tensors(solver)
|
|
460
|
+
ex_decay, ey_decay, ez_decay = _electric_decay_tensors(solver)
|
|
461
|
+
solver.fdtd_module.updateElectricFieldExStandard3D(
|
|
462
|
+
Ex=ex,
|
|
463
|
+
Hy=hy,
|
|
464
|
+
Hz=hz,
|
|
465
|
+
ExDecay=ex_decay,
|
|
466
|
+
ExCurl=ex_curl,
|
|
467
|
+
invDy=solver.inv_dy_e,
|
|
468
|
+
invDz=solver.inv_dz_e,
|
|
469
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
470
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
471
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
472
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
473
|
+
).launchRaw()
|
|
474
|
+
solver.fdtd_module.updateElectricFieldEyStandard3D(
|
|
475
|
+
Ey=ey,
|
|
476
|
+
Hx=hx,
|
|
477
|
+
Hz=hz,
|
|
478
|
+
EyDecay=ey_decay,
|
|
479
|
+
EyCurl=ey_curl,
|
|
480
|
+
invDx=solver.inv_dx_e,
|
|
481
|
+
invDz=solver.inv_dz_e,
|
|
482
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
483
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
484
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
485
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
486
|
+
).launchRaw()
|
|
487
|
+
solver.fdtd_module.updateElectricFieldEzStandard3D(
|
|
488
|
+
Ez=ez,
|
|
489
|
+
Hx=hx,
|
|
490
|
+
Hy=hy,
|
|
491
|
+
EzDecay=ez_decay,
|
|
492
|
+
EzCurl=ez_curl,
|
|
493
|
+
invDx=solver.inv_dx_e,
|
|
494
|
+
invDy=solver.inv_dy_e,
|
|
495
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
496
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
497
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
498
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
499
|
+
).launchRaw()
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def update_electric_fields_modulated_standard(solver, ex, ey, ez, hx, hy, hz, time_value):
|
|
503
|
+
# Composing modulation with an instantaneous nonlinearity: the modulation
|
|
504
|
+
# scales the same field-dependent decay/curl coefficients the nonlinear kernel
|
|
505
|
+
# rewrote this step (static tensors when no nonlinearity is present).
|
|
506
|
+
ex_curl, ey_curl, ez_curl = _electric_curl_tensors(solver)
|
|
507
|
+
ex_decay, ey_decay, ez_decay = _electric_decay_tensors(solver)
|
|
508
|
+
solver.fdtd_module.updateElectricFieldExModulated3D(
|
|
509
|
+
Ex=ex,
|
|
510
|
+
Hy=hy,
|
|
511
|
+
Hz=hz,
|
|
512
|
+
ExDecay=ex_decay,
|
|
513
|
+
ExCurl=ex_curl,
|
|
514
|
+
ModCos=solver.mod_cos_Ex,
|
|
515
|
+
ModSin=solver.mod_sin_Ex,
|
|
516
|
+
ModOmega=solver.mod_omega_Ex,
|
|
517
|
+
ModulationTime=solver._modulation_time,
|
|
518
|
+
invDy=solver.inv_dy_e,
|
|
519
|
+
invDz=solver.inv_dz_e,
|
|
520
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
521
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
522
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
523
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
524
|
+
).launchRaw()
|
|
525
|
+
solver.fdtd_module.updateElectricFieldEyModulated3D(
|
|
526
|
+
Ey=ey,
|
|
527
|
+
Hx=hx,
|
|
528
|
+
Hz=hz,
|
|
529
|
+
EyDecay=ey_decay,
|
|
530
|
+
EyCurl=ey_curl,
|
|
531
|
+
ModCos=solver.mod_cos_Ey,
|
|
532
|
+
ModSin=solver.mod_sin_Ey,
|
|
533
|
+
ModOmega=solver.mod_omega_Ey,
|
|
534
|
+
ModulationTime=solver._modulation_time,
|
|
535
|
+
invDx=solver.inv_dx_e,
|
|
536
|
+
invDz=solver.inv_dz_e,
|
|
537
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
538
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
539
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
540
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
541
|
+
).launchRaw()
|
|
542
|
+
solver.fdtd_module.updateElectricFieldEzModulated3D(
|
|
543
|
+
Ez=ez,
|
|
544
|
+
Hx=hx,
|
|
545
|
+
Hy=hy,
|
|
546
|
+
EzDecay=ez_decay,
|
|
547
|
+
EzCurl=ez_curl,
|
|
548
|
+
ModCos=solver.mod_cos_Ez,
|
|
549
|
+
ModSin=solver.mod_sin_Ez,
|
|
550
|
+
ModOmega=solver.mod_omega_Ez,
|
|
551
|
+
ModulationTime=solver._modulation_time,
|
|
552
|
+
invDx=solver.inv_dx_e,
|
|
553
|
+
invDy=solver.inv_dy_e,
|
|
554
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
555
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
556
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
557
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
558
|
+
).launchRaw()
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def update_electric_fields_modulated_cpml(solver, ex, ey, ez, hx, hy, hz, time_value):
|
|
562
|
+
if solver._cpml_memory_mode == "dense":
|
|
563
|
+
update_electric_fields_modulated_cpml_dense(solver, ex, ey, ez, hx, hy, hz, time_value)
|
|
564
|
+
return
|
|
565
|
+
update_electric_fields_modulated_cpml_compressed(solver, ex, ey, ez, hx, hy, hz, time_value)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def update_electric_fields_modulated_cpml_dense(solver, ex, ey, ez, hx, hy, hz, time_value):
|
|
569
|
+
ex_curl, ey_curl, ez_curl = _electric_curl_tensors(solver)
|
|
570
|
+
ex_decay, ey_decay, ez_decay = _electric_decay_tensors(solver)
|
|
571
|
+
solver.fdtd_module.updateElectricFieldExCpmlModulated3D(
|
|
572
|
+
Ex=ex,
|
|
573
|
+
Hy=hy,
|
|
574
|
+
Hz=hz,
|
|
575
|
+
ExDecay=ex_decay,
|
|
576
|
+
ExCurl=ex_curl,
|
|
577
|
+
ModCos=solver.mod_cos_Ex,
|
|
578
|
+
ModSin=solver.mod_sin_Ex,
|
|
579
|
+
ModOmega=solver.mod_omega_Ex,
|
|
580
|
+
ModulationTime=solver._modulation_time,
|
|
581
|
+
PsiExY=solver.psi_ex_y,
|
|
582
|
+
PsiExZ=solver.psi_ex_z,
|
|
583
|
+
InvKappaExY=solver.cpml_inv_kappa_e_y,
|
|
584
|
+
BExY=solver.cpml_b_e_y,
|
|
585
|
+
CExY=solver.cpml_c_e_y,
|
|
586
|
+
InvKappaExZ=solver.cpml_inv_kappa_e_z,
|
|
587
|
+
BExZ=solver.cpml_b_e_z,
|
|
588
|
+
CExZ=solver.cpml_c_e_z,
|
|
589
|
+
invDy=solver.inv_dy_e,
|
|
590
|
+
invDz=solver.inv_dz_e,
|
|
591
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
592
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
593
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
594
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
595
|
+
).launchRaw()
|
|
596
|
+
solver.fdtd_module.updateElectricFieldEyCpmlModulated3D(
|
|
597
|
+
Ey=ey,
|
|
598
|
+
Hx=hx,
|
|
599
|
+
Hz=hz,
|
|
600
|
+
EyDecay=ey_decay,
|
|
601
|
+
EyCurl=ey_curl,
|
|
602
|
+
ModCos=solver.mod_cos_Ey,
|
|
603
|
+
ModSin=solver.mod_sin_Ey,
|
|
604
|
+
ModOmega=solver.mod_omega_Ey,
|
|
605
|
+
ModulationTime=solver._modulation_time,
|
|
606
|
+
PsiEyX=solver.psi_ey_x,
|
|
607
|
+
PsiEyZ=solver.psi_ey_z,
|
|
608
|
+
InvKappaEyX=solver.cpml_inv_kappa_e_x,
|
|
609
|
+
BEyX=solver.cpml_b_e_x,
|
|
610
|
+
CEyX=solver.cpml_c_e_x,
|
|
611
|
+
InvKappaEyZ=solver.cpml_inv_kappa_e_z,
|
|
612
|
+
BEyZ=solver.cpml_b_e_z,
|
|
613
|
+
CEyZ=solver.cpml_c_e_z,
|
|
614
|
+
invDx=solver.inv_dx_e,
|
|
615
|
+
invDz=solver.inv_dz_e,
|
|
616
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
617
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
618
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
619
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
620
|
+
).launchRaw()
|
|
621
|
+
solver.fdtd_module.updateElectricFieldEzCpmlModulated3D(
|
|
622
|
+
Ez=ez,
|
|
623
|
+
Hx=hx,
|
|
624
|
+
Hy=hy,
|
|
625
|
+
EzDecay=ez_decay,
|
|
626
|
+
EzCurl=ez_curl,
|
|
627
|
+
ModCos=solver.mod_cos_Ez,
|
|
628
|
+
ModSin=solver.mod_sin_Ez,
|
|
629
|
+
ModOmega=solver.mod_omega_Ez,
|
|
630
|
+
ModulationTime=solver._modulation_time,
|
|
631
|
+
PsiEzX=solver.psi_ez_x,
|
|
632
|
+
PsiEzY=solver.psi_ez_y,
|
|
633
|
+
InvKappaEzX=solver.cpml_inv_kappa_e_x,
|
|
634
|
+
BEzX=solver.cpml_b_e_x,
|
|
635
|
+
CEzX=solver.cpml_c_e_x,
|
|
636
|
+
InvKappaEzY=solver.cpml_inv_kappa_e_y,
|
|
637
|
+
BEzY=solver.cpml_b_e_y,
|
|
638
|
+
CEzY=solver.cpml_c_e_y,
|
|
639
|
+
invDx=solver.inv_dx_e,
|
|
640
|
+
invDy=solver.inv_dy_e,
|
|
641
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
642
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
643
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
644
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
645
|
+
).launchRaw()
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def update_electric_fields_modulated_cpml_compressed(solver, ex, ey, ez, hx, hy, hz, time_value):
|
|
649
|
+
# Compressed (slab) psi layout: thread the per-side narrow()/offset bookkeeping
|
|
650
|
+
# (cpml_layout_params) through the modulated kernels, exactly as the plain
|
|
651
|
+
# compressed CPML update does, with the modulation phase factors applied on top.
|
|
652
|
+
psi_ex_y_low, psi_ex_y_high_start, psi_ex_y_high = cpml_layout_params(solver, "psi_ex_y")
|
|
653
|
+
psi_ex_z_low, psi_ex_z_high_start, psi_ex_z_high = cpml_layout_params(solver, "psi_ex_z")
|
|
654
|
+
psi_ey_x_low, psi_ey_x_high_start, psi_ey_x_high = cpml_layout_params(solver, "psi_ey_x")
|
|
655
|
+
psi_ey_z_low, psi_ey_z_high_start, psi_ey_z_high = cpml_layout_params(solver, "psi_ey_z")
|
|
656
|
+
psi_ez_x_low, psi_ez_x_high_start, psi_ez_x_high = cpml_layout_params(solver, "psi_ez_x")
|
|
657
|
+
psi_ez_y_low, psi_ez_y_high_start, psi_ez_y_high = cpml_layout_params(solver, "psi_ez_y")
|
|
658
|
+
ex_curl, ey_curl, ez_curl = _electric_curl_tensors(solver)
|
|
659
|
+
ex_decay, ey_decay, ez_decay = _electric_decay_tensors(solver)
|
|
660
|
+
solver.fdtd_module.updateElectricFieldExCpmlModulatedCompressed3D(
|
|
661
|
+
Ex=ex,
|
|
662
|
+
Hy=hy,
|
|
663
|
+
Hz=hz,
|
|
664
|
+
ExDecay=ex_decay,
|
|
665
|
+
ExCurl=ex_curl,
|
|
666
|
+
ModCos=solver.mod_cos_Ex,
|
|
667
|
+
ModSin=solver.mod_sin_Ex,
|
|
668
|
+
ModOmega=solver.mod_omega_Ex,
|
|
669
|
+
ModulationTime=solver._modulation_time,
|
|
670
|
+
PsiExY=solver.psi_ex_y,
|
|
671
|
+
PsiExZ=solver.psi_ex_z,
|
|
672
|
+
InvKappaExY=solver.cpml_inv_kappa_e_y,
|
|
673
|
+
BExY=solver.cpml_b_e_y,
|
|
674
|
+
CExY=solver.cpml_c_e_y,
|
|
675
|
+
InvKappaExZ=solver.cpml_inv_kappa_e_z,
|
|
676
|
+
BExZ=solver.cpml_b_e_z,
|
|
677
|
+
CExZ=solver.cpml_c_e_z,
|
|
678
|
+
invDy=solver.inv_dy_e,
|
|
679
|
+
invDz=solver.inv_dz_e,
|
|
680
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
681
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
682
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
683
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
684
|
+
psiExYLowLength=psi_ex_y_low,
|
|
685
|
+
psiExYHighStart=psi_ex_y_high_start,
|
|
686
|
+
psiExYHighLength=psi_ex_y_high,
|
|
687
|
+
psiExZLowLength=psi_ex_z_low,
|
|
688
|
+
psiExZHighStart=psi_ex_z_high_start,
|
|
689
|
+
psiExZHighLength=psi_ex_z_high,
|
|
690
|
+
).launchRaw()
|
|
691
|
+
solver.fdtd_module.updateElectricFieldEyCpmlModulatedCompressed3D(
|
|
692
|
+
Ey=ey,
|
|
693
|
+
Hx=hx,
|
|
694
|
+
Hz=hz,
|
|
695
|
+
EyDecay=ey_decay,
|
|
696
|
+
EyCurl=ey_curl,
|
|
697
|
+
ModCos=solver.mod_cos_Ey,
|
|
698
|
+
ModSin=solver.mod_sin_Ey,
|
|
699
|
+
ModOmega=solver.mod_omega_Ey,
|
|
700
|
+
ModulationTime=solver._modulation_time,
|
|
701
|
+
PsiEyX=solver.psi_ey_x,
|
|
702
|
+
PsiEyZ=solver.psi_ey_z,
|
|
703
|
+
InvKappaEyX=solver.cpml_inv_kappa_e_x,
|
|
704
|
+
BEyX=solver.cpml_b_e_x,
|
|
705
|
+
CEyX=solver.cpml_c_e_x,
|
|
706
|
+
InvKappaEyZ=solver.cpml_inv_kappa_e_z,
|
|
707
|
+
BEyZ=solver.cpml_b_e_z,
|
|
708
|
+
CEyZ=solver.cpml_c_e_z,
|
|
709
|
+
invDx=solver.inv_dx_e,
|
|
710
|
+
invDz=solver.inv_dz_e,
|
|
711
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
712
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
713
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
714
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
715
|
+
psiEyXLowLength=psi_ey_x_low,
|
|
716
|
+
psiEyXHighStart=psi_ey_x_high_start,
|
|
717
|
+
psiEyXHighLength=psi_ey_x_high,
|
|
718
|
+
psiEyZLowLength=psi_ey_z_low,
|
|
719
|
+
psiEyZHighStart=psi_ey_z_high_start,
|
|
720
|
+
psiEyZHighLength=psi_ey_z_high,
|
|
721
|
+
).launchRaw()
|
|
722
|
+
solver.fdtd_module.updateElectricFieldEzCpmlModulatedCompressed3D(
|
|
723
|
+
Ez=ez,
|
|
724
|
+
Hx=hx,
|
|
725
|
+
Hy=hy,
|
|
726
|
+
EzDecay=ez_decay,
|
|
727
|
+
EzCurl=ez_curl,
|
|
728
|
+
ModCos=solver.mod_cos_Ez,
|
|
729
|
+
ModSin=solver.mod_sin_Ez,
|
|
730
|
+
ModOmega=solver.mod_omega_Ez,
|
|
731
|
+
ModulationTime=solver._modulation_time,
|
|
732
|
+
PsiEzX=solver.psi_ez_x,
|
|
733
|
+
PsiEzY=solver.psi_ez_y,
|
|
734
|
+
InvKappaEzX=solver.cpml_inv_kappa_e_x,
|
|
735
|
+
BEzX=solver.cpml_b_e_x,
|
|
736
|
+
CEzX=solver.cpml_c_e_x,
|
|
737
|
+
InvKappaEzY=solver.cpml_inv_kappa_e_y,
|
|
738
|
+
BEzY=solver.cpml_b_e_y,
|
|
739
|
+
CEzY=solver.cpml_c_e_y,
|
|
740
|
+
invDx=solver.inv_dx_e,
|
|
741
|
+
invDy=solver.inv_dy_e,
|
|
742
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
743
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
744
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
745
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
746
|
+
psiEzXLowLength=psi_ez_x_low,
|
|
747
|
+
psiEzXHighStart=psi_ez_x_high_start,
|
|
748
|
+
psiEzXHighLength=psi_ez_x_high,
|
|
749
|
+
psiEzYLowLength=psi_ez_y_low,
|
|
750
|
+
psiEzYHighStart=psi_ez_y_high_start,
|
|
751
|
+
psiEzYHighLength=psi_ez_y_high,
|
|
752
|
+
).launchRaw()
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def update_electric_fields(solver, ex, ey, ez, hx, hy, hz, *, time_value=None):
|
|
756
|
+
if getattr(solver, "modulation_enabled", False):
|
|
757
|
+
if time_value is None:
|
|
758
|
+
raise RuntimeError(
|
|
759
|
+
"The time-modulated electric update requires the current step time_value."
|
|
760
|
+
)
|
|
761
|
+
if solver.uses_cpml:
|
|
762
|
+
update_electric_fields_modulated_cpml(solver, ex, ey, ez, hx, hy, hz, time_value)
|
|
763
|
+
else:
|
|
764
|
+
update_electric_fields_modulated_standard(solver, ex, ey, ez, hx, hy, hz, time_value)
|
|
765
|
+
return
|
|
766
|
+
if solver.uses_cpml:
|
|
767
|
+
update_electric_fields_cpml(solver, ex, ey, ez, hx, hy, hz)
|
|
768
|
+
else:
|
|
769
|
+
update_electric_fields_standard(solver, ex, ey, ez, hx, hy, hz)
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def _full_aniso_periodic_flags(solver):
|
|
773
|
+
return (
|
|
774
|
+
int(solver.boundary_x_low_code == BOUNDARY_PERIODIC and solver.boundary_x_high_code == BOUNDARY_PERIODIC),
|
|
775
|
+
int(solver.boundary_y_low_code == BOUNDARY_PERIODIC and solver.boundary_y_high_code == BOUNDARY_PERIODIC),
|
|
776
|
+
int(solver.boundary_z_low_code == BOUNDARY_PERIODIC and solver.boundary_z_high_code == BOUNDARY_PERIODIC),
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
|
|
780
|
+
def apply_full_aniso_corrections(solver):
|
|
781
|
+
"""Add the off-diagonal anisotropic coupling terms to the E update.
|
|
782
|
+
|
|
783
|
+
Each kernel neighbor-averages the two off-axis curl(H) components onto the
|
|
784
|
+
target Yee edge and accumulates ``coeff * <curlH>``; the coefficients are the
|
|
785
|
+
off-diagonal entries of the per-edge inverse permittivity tensor scaled by
|
|
786
|
+
``dt/eps0`` and vanish outside the anisotropic structures. Periodic axes
|
|
787
|
+
wrap the collocation stencil; other boundaries skip out-of-range samples.
|
|
788
|
+
Under CPML the coordinate-stretched variant is used so the coupling is
|
|
789
|
+
absorbed consistently where an anisotropic structure reaches the boundary.
|
|
790
|
+
"""
|
|
791
|
+
if getattr(solver, "uses_cpml", False):
|
|
792
|
+
apply_full_aniso_corrections_cpml(solver)
|
|
793
|
+
return
|
|
794
|
+
periodic_x, periodic_y, periodic_z = _full_aniso_periodic_flags(solver)
|
|
795
|
+
solver.fdtd_module.updateElectricFieldExFullAniso3D(
|
|
796
|
+
Ex=solver.Ex,
|
|
797
|
+
Hx=solver.Hx,
|
|
798
|
+
Hy=solver.Hy,
|
|
799
|
+
Hz=solver.Hz,
|
|
800
|
+
CoeffY=solver.cex_aniso_y,
|
|
801
|
+
CoeffZ=solver.cex_aniso_z,
|
|
802
|
+
invDx=solver.inv_dx_e,
|
|
803
|
+
invDy=solver.inv_dy_e,
|
|
804
|
+
invDz=solver.inv_dz_e,
|
|
805
|
+
periodicX=periodic_x,
|
|
806
|
+
periodicY=periodic_y,
|
|
807
|
+
periodicZ=periodic_z,
|
|
808
|
+
).launchRaw()
|
|
809
|
+
solver.fdtd_module.updateElectricFieldEyFullAniso3D(
|
|
810
|
+
Ey=solver.Ey,
|
|
811
|
+
Hx=solver.Hx,
|
|
812
|
+
Hy=solver.Hy,
|
|
813
|
+
Hz=solver.Hz,
|
|
814
|
+
CoeffX=solver.cey_aniso_x,
|
|
815
|
+
CoeffZ=solver.cey_aniso_z,
|
|
816
|
+
invDx=solver.inv_dx_e,
|
|
817
|
+
invDy=solver.inv_dy_e,
|
|
818
|
+
invDz=solver.inv_dz_e,
|
|
819
|
+
periodicX=periodic_x,
|
|
820
|
+
periodicY=periodic_y,
|
|
821
|
+
periodicZ=periodic_z,
|
|
822
|
+
).launchRaw()
|
|
823
|
+
solver.fdtd_module.updateElectricFieldEzFullAniso3D(
|
|
824
|
+
Ez=solver.Ez,
|
|
825
|
+
Hx=solver.Hx,
|
|
826
|
+
Hy=solver.Hy,
|
|
827
|
+
Hz=solver.Hz,
|
|
828
|
+
CoeffX=solver.cez_aniso_x,
|
|
829
|
+
CoeffY=solver.cez_aniso_y,
|
|
830
|
+
invDx=solver.inv_dx_e,
|
|
831
|
+
invDy=solver.inv_dy_e,
|
|
832
|
+
invDz=solver.inv_dz_e,
|
|
833
|
+
periodicX=periodic_x,
|
|
834
|
+
periodicY=periodic_y,
|
|
835
|
+
periodicZ=periodic_z,
|
|
836
|
+
).launchRaw()
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
def apply_full_aniso_corrections_cpml(solver):
|
|
840
|
+
"""CPML-consistent off-diagonal anisotropic coupling for absorber overlap.
|
|
841
|
+
|
|
842
|
+
Each spatial derivative that enters the collocated off-axis curl(H) is
|
|
843
|
+
coordinate-stretched with its own recursive-convolution memory owned by the
|
|
844
|
+
target E edge. The two transverse directions use the E-field (node) CPML
|
|
845
|
+
profiles at the edge index; the edge's own direction sits on a half point and
|
|
846
|
+
uses the H-field (half) profile. Outside the absorber the profiles are neutral
|
|
847
|
+
and the psi buffers stay zero, so the correction matches the raw update.
|
|
848
|
+
"""
|
|
849
|
+
periodic_x, periodic_y, periodic_z = _full_aniso_periodic_flags(solver)
|
|
850
|
+
solver.fdtd_module.updateElectricFieldExFullAnisoCpml3D(
|
|
851
|
+
Ex=solver.Ex,
|
|
852
|
+
Hx=solver.Hx,
|
|
853
|
+
Hy=solver.Hy,
|
|
854
|
+
Hz=solver.Hz,
|
|
855
|
+
CoeffY=solver.cex_aniso_y,
|
|
856
|
+
CoeffZ=solver.cex_aniso_z,
|
|
857
|
+
invDx=solver.inv_dx_e,
|
|
858
|
+
invDy=solver.inv_dy_e,
|
|
859
|
+
invDz=solver.inv_dz_e,
|
|
860
|
+
InvKappaX=solver.cpml_inv_kappa_h_x,
|
|
861
|
+
BX=solver.cpml_b_h_x,
|
|
862
|
+
CX=solver.cpml_c_h_x,
|
|
863
|
+
InvKappaY=solver.cpml_inv_kappa_e_y,
|
|
864
|
+
BY=solver.cpml_b_e_y,
|
|
865
|
+
CY=solver.cpml_c_e_y,
|
|
866
|
+
InvKappaZ=solver.cpml_inv_kappa_e_z,
|
|
867
|
+
BZ=solver.cpml_b_e_z,
|
|
868
|
+
CZ=solver.cpml_c_e_z,
|
|
869
|
+
periodicX=periodic_x,
|
|
870
|
+
periodicY=periodic_y,
|
|
871
|
+
periodicZ=periodic_z,
|
|
872
|
+
PsiX=solver.psi_ex_aniso_x,
|
|
873
|
+
PsiY=solver.psi_ex_aniso_y,
|
|
874
|
+
PsiZ=solver.psi_ex_aniso_z,
|
|
875
|
+
).launchRaw()
|
|
876
|
+
solver.fdtd_module.updateElectricFieldEyFullAnisoCpml3D(
|
|
877
|
+
Ey=solver.Ey,
|
|
878
|
+
Hx=solver.Hx,
|
|
879
|
+
Hy=solver.Hy,
|
|
880
|
+
Hz=solver.Hz,
|
|
881
|
+
CoeffX=solver.cey_aniso_x,
|
|
882
|
+
CoeffZ=solver.cey_aniso_z,
|
|
883
|
+
invDx=solver.inv_dx_e,
|
|
884
|
+
invDy=solver.inv_dy_e,
|
|
885
|
+
invDz=solver.inv_dz_e,
|
|
886
|
+
InvKappaX=solver.cpml_inv_kappa_e_x,
|
|
887
|
+
BX=solver.cpml_b_e_x,
|
|
888
|
+
CX=solver.cpml_c_e_x,
|
|
889
|
+
InvKappaY=solver.cpml_inv_kappa_h_y,
|
|
890
|
+
BY=solver.cpml_b_h_y,
|
|
891
|
+
CY=solver.cpml_c_h_y,
|
|
892
|
+
InvKappaZ=solver.cpml_inv_kappa_e_z,
|
|
893
|
+
BZ=solver.cpml_b_e_z,
|
|
894
|
+
CZ=solver.cpml_c_e_z,
|
|
895
|
+
periodicX=periodic_x,
|
|
896
|
+
periodicY=periodic_y,
|
|
897
|
+
periodicZ=periodic_z,
|
|
898
|
+
PsiX=solver.psi_ey_aniso_x,
|
|
899
|
+
PsiY=solver.psi_ey_aniso_y,
|
|
900
|
+
PsiZ=solver.psi_ey_aniso_z,
|
|
901
|
+
).launchRaw()
|
|
902
|
+
solver.fdtd_module.updateElectricFieldEzFullAnisoCpml3D(
|
|
903
|
+
Ez=solver.Ez,
|
|
904
|
+
Hx=solver.Hx,
|
|
905
|
+
Hy=solver.Hy,
|
|
906
|
+
Hz=solver.Hz,
|
|
907
|
+
CoeffX=solver.cez_aniso_x,
|
|
908
|
+
CoeffY=solver.cez_aniso_y,
|
|
909
|
+
invDx=solver.inv_dx_e,
|
|
910
|
+
invDy=solver.inv_dy_e,
|
|
911
|
+
invDz=solver.inv_dz_e,
|
|
912
|
+
InvKappaX=solver.cpml_inv_kappa_e_x,
|
|
913
|
+
BX=solver.cpml_b_e_x,
|
|
914
|
+
CX=solver.cpml_c_e_x,
|
|
915
|
+
InvKappaY=solver.cpml_inv_kappa_e_y,
|
|
916
|
+
BY=solver.cpml_b_e_y,
|
|
917
|
+
CY=solver.cpml_c_e_y,
|
|
918
|
+
InvKappaZ=solver.cpml_inv_kappa_h_z,
|
|
919
|
+
BZ=solver.cpml_b_h_z,
|
|
920
|
+
CZ=solver.cpml_c_h_z,
|
|
921
|
+
periodicX=periodic_x,
|
|
922
|
+
periodicY=periodic_y,
|
|
923
|
+
periodicZ=periodic_z,
|
|
924
|
+
PsiX=solver.psi_ez_aniso_x,
|
|
925
|
+
PsiY=solver.psi_ez_aniso_y,
|
|
926
|
+
PsiZ=solver.psi_ez_aniso_z,
|
|
927
|
+
).launchRaw()
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
def _full_aniso_conductive(solver) -> bool:
|
|
931
|
+
return getattr(solver, "full_aniso_enabled", False) and getattr(solver, "_aniso_cond_current", None) is not None
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
def capture_aniso_conduction_currents(solver):
|
|
935
|
+
"""Snapshot the conduction current sigma . E^n before the electric update.
|
|
936
|
+
|
|
937
|
+
For a full (off-diagonal) anisotropic conductive medium the exact semi-implicit
|
|
938
|
+
update is ``E^{n+1} = E^n + B . (curl H - sigma . E^n)`` with
|
|
939
|
+
``B = dt (eps_inf + dt/2 diag(sigma))^-1``. The diagonal ``-B_ii sigma_i E_i^n``
|
|
940
|
+
term is folded into the decay coefficient, but the off-diagonal
|
|
941
|
+
``-B_ij sigma_j E_j^n`` (i != j) terms need the pre-update field, which the base
|
|
942
|
+
update overwrites, so they are captured here into ``_aniso_cond_current``.
|
|
943
|
+
"""
|
|
944
|
+
if not _full_aniso_conductive(solver):
|
|
945
|
+
return
|
|
946
|
+
currents = solver._aniso_cond_current
|
|
947
|
+
solver.fdtd_module.captureAnisoConductionCurrent3D(
|
|
948
|
+
SigmaX=solver.sigma_e_Ex,
|
|
949
|
+
SigmaY=solver.sigma_e_Ey,
|
|
950
|
+
SigmaZ=solver.sigma_e_Ez,
|
|
951
|
+
Ex=solver.Ex,
|
|
952
|
+
Ey=solver.Ey,
|
|
953
|
+
Ez=solver.Ez,
|
|
954
|
+
Jx=currents["Ex"],
|
|
955
|
+
Jy=currents["Ey"],
|
|
956
|
+
Jz=currents["Ez"],
|
|
957
|
+
).launchRaw()
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
def apply_full_aniso_conduction(solver):
|
|
961
|
+
"""Subtract the off-diagonal conduction current through the inverse tensor.
|
|
962
|
+
|
|
963
|
+
Applies ``E_i -= 0.25 * (B_ij <sigma_j E_j^n> + B_ik <sigma_k E_k^n>)`` with the
|
|
964
|
+
same off-diagonal coefficients (``c*_aniso_*`` == ``B_ij``) and four-neighbor
|
|
965
|
+
collocation used for the curl(H) coupling. The conduction current is pointwise
|
|
966
|
+
(no spatial derivative), so it needs no CPML coordinate stretch and reuses the
|
|
967
|
+
plain off-diagonal current kernels for every boundary configuration.
|
|
968
|
+
"""
|
|
969
|
+
if not _full_aniso_conductive(solver):
|
|
970
|
+
return
|
|
971
|
+
currents = solver._aniso_cond_current
|
|
972
|
+
periodic_x, periodic_y, periodic_z = _full_aniso_periodic_flags(solver)
|
|
973
|
+
solver.fdtd_module.applyAnisoOffdiagCurrentEx3D(
|
|
974
|
+
Ex=solver.Ex,
|
|
975
|
+
Jy=currents["Ey"],
|
|
976
|
+
Jz=currents["Ez"],
|
|
977
|
+
CoeffY=solver.cex_aniso_y,
|
|
978
|
+
CoeffZ=solver.cex_aniso_z,
|
|
979
|
+
periodicX=periodic_x,
|
|
980
|
+
periodicY=periodic_y,
|
|
981
|
+
periodicZ=periodic_z,
|
|
982
|
+
).launchRaw()
|
|
983
|
+
solver.fdtd_module.applyAnisoOffdiagCurrentEy3D(
|
|
984
|
+
Ey=solver.Ey,
|
|
985
|
+
Jx=currents["Ex"],
|
|
986
|
+
Jz=currents["Ez"],
|
|
987
|
+
CoeffX=solver.cey_aniso_x,
|
|
988
|
+
CoeffZ=solver.cey_aniso_z,
|
|
989
|
+
periodicX=periodic_x,
|
|
990
|
+
periodicY=periodic_y,
|
|
991
|
+
periodicZ=periodic_z,
|
|
992
|
+
).launchRaw()
|
|
993
|
+
solver.fdtd_module.applyAnisoOffdiagCurrentEz3D(
|
|
994
|
+
Ez=solver.Ez,
|
|
995
|
+
Jx=currents["Ex"],
|
|
996
|
+
Jy=currents["Ey"],
|
|
997
|
+
CoeffX=solver.cez_aniso_x,
|
|
998
|
+
CoeffY=solver.cez_aniso_y,
|
|
999
|
+
periodicX=periodic_x,
|
|
1000
|
+
periodicY=periodic_y,
|
|
1001
|
+
periodicZ=periodic_z,
|
|
1002
|
+
).launchRaw()
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
def update_electric_fields_bloch(solver):
|
|
1006
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
1007
|
+
raise NotImplementedError(
|
|
1008
|
+
"FDTD instantaneous nonlinear media cannot evolve on the complex Bloch fields: the "
|
|
1009
|
+
"|E|^2 Kerr/chi2/TPA polarization couples the single-wavevector Bloch field to the 2k/3k "
|
|
1010
|
+
"harmonic sectors a complex-Bloch run does not carry, so the nonlinear response is not "
|
|
1011
|
+
"closed on the real update kernels. Use a real-field boundary (PML/PEC/PMC/periodic)."
|
|
1012
|
+
)
|
|
1013
|
+
solver.fdtd_module.updateElectricFieldExBloch3D(
|
|
1014
|
+
ExReal=solver.Ex,
|
|
1015
|
+
ExImag=solver.Ex_imag,
|
|
1016
|
+
HyReal=solver.Hy,
|
|
1017
|
+
HyImag=solver.Hy_imag,
|
|
1018
|
+
HzReal=solver.Hz,
|
|
1019
|
+
HzImag=solver.Hz_imag,
|
|
1020
|
+
ExDecay=solver.cex_decay,
|
|
1021
|
+
ExCurl=solver.cex_curl,
|
|
1022
|
+
phaseCosY=solver.boundary_phase_cos[1],
|
|
1023
|
+
phaseSinY=solver.boundary_phase_sin[1],
|
|
1024
|
+
phaseCosZ=solver.boundary_phase_cos[2],
|
|
1025
|
+
phaseSinZ=solver.boundary_phase_sin[2],
|
|
1026
|
+
invDy=solver.inv_dy_e,
|
|
1027
|
+
invDz=solver.inv_dz_e,
|
|
1028
|
+
).launchRaw()
|
|
1029
|
+
solver.fdtd_module.updateElectricFieldEyBloch3D(
|
|
1030
|
+
EyReal=solver.Ey,
|
|
1031
|
+
EyImag=solver.Ey_imag,
|
|
1032
|
+
HxReal=solver.Hx,
|
|
1033
|
+
HxImag=solver.Hx_imag,
|
|
1034
|
+
HzReal=solver.Hz,
|
|
1035
|
+
HzImag=solver.Hz_imag,
|
|
1036
|
+
EyDecay=solver.cey_decay,
|
|
1037
|
+
EyCurl=solver.cey_curl,
|
|
1038
|
+
phaseCosX=solver.boundary_phase_cos[0],
|
|
1039
|
+
phaseSinX=solver.boundary_phase_sin[0],
|
|
1040
|
+
phaseCosZ=solver.boundary_phase_cos[2],
|
|
1041
|
+
phaseSinZ=solver.boundary_phase_sin[2],
|
|
1042
|
+
invDx=solver.inv_dx_e,
|
|
1043
|
+
invDz=solver.inv_dz_e,
|
|
1044
|
+
).launchRaw()
|
|
1045
|
+
solver.fdtd_module.updateElectricFieldEzBloch3D(
|
|
1046
|
+
EzReal=solver.Ez,
|
|
1047
|
+
EzImag=solver.Ez_imag,
|
|
1048
|
+
HxReal=solver.Hx,
|
|
1049
|
+
HxImag=solver.Hx_imag,
|
|
1050
|
+
HyReal=solver.Hy,
|
|
1051
|
+
HyImag=solver.Hy_imag,
|
|
1052
|
+
EzDecay=solver.cez_decay,
|
|
1053
|
+
EzCurl=solver.cez_curl,
|
|
1054
|
+
phaseCosX=solver.boundary_phase_cos[0],
|
|
1055
|
+
phaseSinX=solver.boundary_phase_sin[0],
|
|
1056
|
+
phaseCosY=solver.boundary_phase_cos[1],
|
|
1057
|
+
phaseSinY=solver.boundary_phase_sin[1],
|
|
1058
|
+
invDx=solver.inv_dx_e,
|
|
1059
|
+
invDy=solver.inv_dy_e,
|
|
1060
|
+
).launchRaw()
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def update_electric_fields_bloch_xy_standard_z(solver):
|
|
1064
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
1065
|
+
raise NotImplementedError(
|
|
1066
|
+
"FDTD instantaneous nonlinear media cannot evolve on the complex Bloch fields: the "
|
|
1067
|
+
"|E|^2 Kerr/chi2/TPA polarization couples the single-wavevector Bloch field to the 2k/3k "
|
|
1068
|
+
"harmonic sectors a complex-Bloch run does not carry, so the nonlinear response is not "
|
|
1069
|
+
"closed on the real update kernels. Use a real-field boundary (PML/PEC/PMC/periodic)."
|
|
1070
|
+
)
|
|
1071
|
+
solver.fdtd_module.updateElectricFieldExBlochYStandardZ3D(
|
|
1072
|
+
ExReal=solver.Ex,
|
|
1073
|
+
ExImag=solver.Ex_imag,
|
|
1074
|
+
HyReal=solver.Hy,
|
|
1075
|
+
HyImag=solver.Hy_imag,
|
|
1076
|
+
HzReal=solver.Hz,
|
|
1077
|
+
HzImag=solver.Hz_imag,
|
|
1078
|
+
ExDecay=solver.cex_decay,
|
|
1079
|
+
ExCurl=solver.cex_curl,
|
|
1080
|
+
phaseCosY=solver.boundary_phase_cos[1],
|
|
1081
|
+
phaseSinY=solver.boundary_phase_sin[1],
|
|
1082
|
+
invDy=solver.inv_dy_e,
|
|
1083
|
+
invDz=solver.inv_dz_e,
|
|
1084
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
1085
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
1086
|
+
).launchRaw()
|
|
1087
|
+
solver.fdtd_module.updateElectricFieldEyBlochXStandardZ3D(
|
|
1088
|
+
EyReal=solver.Ey,
|
|
1089
|
+
EyImag=solver.Ey_imag,
|
|
1090
|
+
HxReal=solver.Hx,
|
|
1091
|
+
HxImag=solver.Hx_imag,
|
|
1092
|
+
HzReal=solver.Hz,
|
|
1093
|
+
HzImag=solver.Hz_imag,
|
|
1094
|
+
EyDecay=solver.cey_decay,
|
|
1095
|
+
EyCurl=solver.cey_curl,
|
|
1096
|
+
phaseCosX=solver.boundary_phase_cos[0],
|
|
1097
|
+
phaseSinX=solver.boundary_phase_sin[0],
|
|
1098
|
+
invDx=solver.inv_dx_e,
|
|
1099
|
+
invDz=solver.inv_dz_e,
|
|
1100
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
1101
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
1102
|
+
).launchRaw()
|
|
1103
|
+
solver.fdtd_module.updateElectricFieldEzBloch3D(
|
|
1104
|
+
EzReal=solver.Ez,
|
|
1105
|
+
EzImag=solver.Ez_imag,
|
|
1106
|
+
HxReal=solver.Hx,
|
|
1107
|
+
HxImag=solver.Hx_imag,
|
|
1108
|
+
HyReal=solver.Hy,
|
|
1109
|
+
HyImag=solver.Hy_imag,
|
|
1110
|
+
EzDecay=solver.cez_decay,
|
|
1111
|
+
EzCurl=solver.cez_curl,
|
|
1112
|
+
phaseCosX=solver.boundary_phase_cos[0],
|
|
1113
|
+
phaseSinX=solver.boundary_phase_sin[0],
|
|
1114
|
+
phaseCosY=solver.boundary_phase_cos[1],
|
|
1115
|
+
phaseSinY=solver.boundary_phase_sin[1],
|
|
1116
|
+
invDx=solver.inv_dx_e,
|
|
1117
|
+
invDy=solver.inv_dy_e,
|
|
1118
|
+
).launchRaw()
|
|
1119
|
+
|
|
1120
|
+
|
|
1121
|
+
def _bloch_cpml_pml_axis(solver):
|
|
1122
|
+
"""Return the single PML axis (``"x"``/``"y"``/``"z"``) of a mixed Bloch+CPML
|
|
1123
|
+
update whose faces are exactly one axis of paired PML and two axes of paired
|
|
1124
|
+
Bloch, or ``None`` for any other face configuration.
|
|
1125
|
+
|
|
1126
|
+
The complex split-field Bloch update carries the transverse wrap phase on the
|
|
1127
|
+
two periodic axes and runs the recursive-convolution CPML stretch on the one
|
|
1128
|
+
absorbing axis; that decomposition exists only for the three single-PML-axis
|
|
1129
|
+
permutations."""
|
|
1130
|
+
axes = ("x", "y", "z")
|
|
1131
|
+
codes = {
|
|
1132
|
+
axis: (
|
|
1133
|
+
int(getattr(solver, f"boundary_{axis}_low_code")),
|
|
1134
|
+
int(getattr(solver, f"boundary_{axis}_high_code")),
|
|
1135
|
+
)
|
|
1136
|
+
for axis in axes
|
|
1137
|
+
}
|
|
1138
|
+
pml_axes = [axis for axis in axes if codes[axis] == (BOUNDARY_PML, BOUNDARY_PML)]
|
|
1139
|
+
bloch_axes = [axis for axis in axes if codes[axis] == (BOUNDARY_BLOCH, BOUNDARY_BLOCH)]
|
|
1140
|
+
if len(pml_axes) == 1 and len(bloch_axes) == 2:
|
|
1141
|
+
return pml_axes[0]
|
|
1142
|
+
return None
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
def _validate_bloch_cpml_update_layout(solver):
|
|
1146
|
+
pml_axis = _bloch_cpml_pml_axis(solver)
|
|
1147
|
+
if pml_axis is None:
|
|
1148
|
+
raise NotImplementedError(
|
|
1149
|
+
"Mixed Bloch/CPML FDTD updates require exactly one PML axis (both faces PML) and two "
|
|
1150
|
+
"Bloch axes (both faces Bloch). The complex split-field Bloch update carries the "
|
|
1151
|
+
"transverse wrap phase on the two periodic axes while the single absorbing axis runs the "
|
|
1152
|
+
"recursive-convolution CPML stretch, so a face layout that mixes Bloch and PML on one axis "
|
|
1153
|
+
"or leaves more than one absorbing axis is not expressible on that split."
|
|
1154
|
+
)
|
|
1155
|
+
return pml_axis
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
def _iter_cpml_field_regions(solver, field, curl, attr_name):
|
|
1159
|
+
layout = getattr(solver, "_cpml_memory_layouts", {}).get(attr_name)
|
|
1160
|
+
if layout is None:
|
|
1161
|
+
yield {
|
|
1162
|
+
"field": field,
|
|
1163
|
+
"curl": curl,
|
|
1164
|
+
"psi": getattr(solver, attr_name),
|
|
1165
|
+
"offsets": (0, 0, 0),
|
|
1166
|
+
}
|
|
1167
|
+
return
|
|
1168
|
+
|
|
1169
|
+
axis = int(layout["axis"])
|
|
1170
|
+
for region in iter_cpml_memory_regions(solver, attr_name):
|
|
1171
|
+
length = int(region["length"])
|
|
1172
|
+
start = int(region["offsets"][axis])
|
|
1173
|
+
yield {
|
|
1174
|
+
"field": field.narrow(axis, start, length),
|
|
1175
|
+
"curl": curl.narrow(axis, start, length),
|
|
1176
|
+
"psi": region["psi"],
|
|
1177
|
+
"offsets": region["offsets"],
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
def _apply_electric_z_cpml_corrections(solver, ex, ey, hx, hy, *, imag=False):
|
|
1182
|
+
psi_ex_z_attr = _cpml_memory_attr("psi_ex_z", imag=imag)
|
|
1183
|
+
psi_ey_z_attr = _cpml_memory_attr("psi_ey_z", imag=imag)
|
|
1184
|
+
for region in _iter_cpml_field_regions(solver, ex, solver.cex_curl, psi_ex_z_attr):
|
|
1185
|
+
offset_i, offset_j, offset_k = region["offsets"]
|
|
1186
|
+
solver.fdtd_module.applyElectricFieldExCpmlZCorrection3D(
|
|
1187
|
+
Ex=region["field"],
|
|
1188
|
+
Hy=hy,
|
|
1189
|
+
ExCurl=region["curl"],
|
|
1190
|
+
PsiExZ=region["psi"],
|
|
1191
|
+
InvKappaExZ=solver.cpml_inv_kappa_e_z,
|
|
1192
|
+
BExZ=solver.cpml_b_e_z,
|
|
1193
|
+
CExZ=solver.cpml_c_e_z,
|
|
1194
|
+
invDz=solver.inv_dz_e,
|
|
1195
|
+
offsetI=offset_i,
|
|
1196
|
+
offsetJ=offset_j,
|
|
1197
|
+
offsetK=offset_k,
|
|
1198
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
1199
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
1200
|
+
fullSizeY=solver.Ex.shape[1],
|
|
1201
|
+
fullSizeZ=solver.Ex.shape[2],
|
|
1202
|
+
).launchRaw()
|
|
1203
|
+
|
|
1204
|
+
for region in _iter_cpml_field_regions(solver, ey, solver.cey_curl, psi_ey_z_attr):
|
|
1205
|
+
offset_i, offset_j, offset_k = region["offsets"]
|
|
1206
|
+
solver.fdtd_module.applyElectricFieldEyCpmlZCorrection3D(
|
|
1207
|
+
Ey=region["field"],
|
|
1208
|
+
Hx=hx,
|
|
1209
|
+
EyCurl=region["curl"],
|
|
1210
|
+
PsiEyZ=region["psi"],
|
|
1211
|
+
InvKappaEyZ=solver.cpml_inv_kappa_e_z,
|
|
1212
|
+
BEyZ=solver.cpml_b_e_z,
|
|
1213
|
+
CEyZ=solver.cpml_c_e_z,
|
|
1214
|
+
invDz=solver.inv_dz_e,
|
|
1215
|
+
offsetI=offset_i,
|
|
1216
|
+
offsetJ=offset_j,
|
|
1217
|
+
offsetK=offset_k,
|
|
1218
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
1219
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
1220
|
+
fullSizeX=solver.Ey.shape[0],
|
|
1221
|
+
fullSizeZ=solver.Ey.shape[2],
|
|
1222
|
+
).launchRaw()
|
|
1223
|
+
|
|
1224
|
+
|
|
1225
|
+
def _apply_electric_y_cpml_corrections(solver, ex, ez, hx, hz, *, imag=False):
|
|
1226
|
+
# PML axis y: Ex takes the recursive-convolution stretch on its d(Hz)/dy term
|
|
1227
|
+
# (tangent Bloch axis z), Ez on its d(Hx)/dy term (tangent Bloch axis x). The
|
|
1228
|
+
# base full-Bloch update already applied the un-stretched derivatives.
|
|
1229
|
+
psi_ex_y_attr = _cpml_memory_attr("psi_ex_y", imag=imag)
|
|
1230
|
+
psi_ez_y_attr = _cpml_memory_attr("psi_ez_y", imag=imag)
|
|
1231
|
+
for region in _iter_cpml_field_regions(solver, ex, solver.cex_curl, psi_ex_y_attr):
|
|
1232
|
+
offset_i, offset_j, offset_k = region["offsets"]
|
|
1233
|
+
solver.fdtd_module.applyElectricFieldExCpmlYCorrection3D(
|
|
1234
|
+
Ex=region["field"],
|
|
1235
|
+
Hz=hz,
|
|
1236
|
+
ExCurl=region["curl"],
|
|
1237
|
+
PsiExY=region["psi"],
|
|
1238
|
+
InvKappaExY=solver.cpml_inv_kappa_e_y,
|
|
1239
|
+
BExY=solver.cpml_b_e_y,
|
|
1240
|
+
CExY=solver.cpml_c_e_y,
|
|
1241
|
+
invDy=solver.inv_dy_e,
|
|
1242
|
+
offsetI=offset_i,
|
|
1243
|
+
offsetJ=offset_j,
|
|
1244
|
+
offsetK=offset_k,
|
|
1245
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
1246
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
1247
|
+
fullSizeZ=solver.Ex.shape[2],
|
|
1248
|
+
fullSizeY=solver.Ex.shape[1],
|
|
1249
|
+
).launchRaw()
|
|
1250
|
+
|
|
1251
|
+
for region in _iter_cpml_field_regions(solver, ez, solver.cez_curl, psi_ez_y_attr):
|
|
1252
|
+
offset_i, offset_j, offset_k = region["offsets"]
|
|
1253
|
+
solver.fdtd_module.applyElectricFieldEzCpmlYCorrection3D(
|
|
1254
|
+
Ez=region["field"],
|
|
1255
|
+
Hx=hx,
|
|
1256
|
+
EzCurl=region["curl"],
|
|
1257
|
+
PsiEzY=region["psi"],
|
|
1258
|
+
InvKappaEzY=solver.cpml_inv_kappa_e_y,
|
|
1259
|
+
BEzY=solver.cpml_b_e_y,
|
|
1260
|
+
CEzY=solver.cpml_c_e_y,
|
|
1261
|
+
invDy=solver.inv_dy_e,
|
|
1262
|
+
offsetI=offset_i,
|
|
1263
|
+
offsetJ=offset_j,
|
|
1264
|
+
offsetK=offset_k,
|
|
1265
|
+
xLowBoundaryMode=solver.boundary_x_low_code,
|
|
1266
|
+
xHighBoundaryMode=solver.boundary_x_high_code,
|
|
1267
|
+
fullSizeX=solver.Ez.shape[0],
|
|
1268
|
+
fullSizeY=solver.Ez.shape[1],
|
|
1269
|
+
).launchRaw()
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
def _apply_electric_x_cpml_corrections(solver, ey, ez, hy, hz, *, imag=False):
|
|
1273
|
+
# PML axis x: Ey takes the stretch on its d(Hz)/dx term (tangent Bloch axis z),
|
|
1274
|
+
# Ez on its d(Hy)/dx term (tangent Bloch axis y).
|
|
1275
|
+
psi_ey_x_attr = _cpml_memory_attr("psi_ey_x", imag=imag)
|
|
1276
|
+
psi_ez_x_attr = _cpml_memory_attr("psi_ez_x", imag=imag)
|
|
1277
|
+
for region in _iter_cpml_field_regions(solver, ey, solver.cey_curl, psi_ey_x_attr):
|
|
1278
|
+
offset_i, offset_j, offset_k = region["offsets"]
|
|
1279
|
+
solver.fdtd_module.applyElectricFieldEyCpmlXCorrection3D(
|
|
1280
|
+
Ey=region["field"],
|
|
1281
|
+
Hz=hz,
|
|
1282
|
+
EyCurl=region["curl"],
|
|
1283
|
+
PsiEyX=region["psi"],
|
|
1284
|
+
InvKappaEyX=solver.cpml_inv_kappa_e_x,
|
|
1285
|
+
BEyX=solver.cpml_b_e_x,
|
|
1286
|
+
CEyX=solver.cpml_c_e_x,
|
|
1287
|
+
invDx=solver.inv_dx_e,
|
|
1288
|
+
offsetI=offset_i,
|
|
1289
|
+
offsetJ=offset_j,
|
|
1290
|
+
offsetK=offset_k,
|
|
1291
|
+
zLowBoundaryMode=solver.boundary_z_low_code,
|
|
1292
|
+
zHighBoundaryMode=solver.boundary_z_high_code,
|
|
1293
|
+
fullSizeZ=solver.Ey.shape[2],
|
|
1294
|
+
fullSizeX=solver.Ey.shape[0],
|
|
1295
|
+
).launchRaw()
|
|
1296
|
+
|
|
1297
|
+
for region in _iter_cpml_field_regions(solver, ez, solver.cez_curl, psi_ez_x_attr):
|
|
1298
|
+
offset_i, offset_j, offset_k = region["offsets"]
|
|
1299
|
+
solver.fdtd_module.applyElectricFieldEzCpmlXCorrection3D(
|
|
1300
|
+
Ez=region["field"],
|
|
1301
|
+
Hy=hy,
|
|
1302
|
+
EzCurl=region["curl"],
|
|
1303
|
+
PsiEzX=region["psi"],
|
|
1304
|
+
InvKappaEzX=solver.cpml_inv_kappa_e_x,
|
|
1305
|
+
BEzX=solver.cpml_b_e_x,
|
|
1306
|
+
CEzX=solver.cpml_c_e_x,
|
|
1307
|
+
invDx=solver.inv_dx_e,
|
|
1308
|
+
offsetI=offset_i,
|
|
1309
|
+
offsetJ=offset_j,
|
|
1310
|
+
offsetK=offset_k,
|
|
1311
|
+
yLowBoundaryMode=solver.boundary_y_low_code,
|
|
1312
|
+
yHighBoundaryMode=solver.boundary_y_high_code,
|
|
1313
|
+
fullSizeY=solver.Ez.shape[1],
|
|
1314
|
+
fullSizeX=solver.Ez.shape[0],
|
|
1315
|
+
).launchRaw()
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
def update_electric_fields_bloch_cpml(solver):
|
|
1319
|
+
pml_axis = _validate_bloch_cpml_update_layout(solver)
|
|
1320
|
+
if pml_axis == "z":
|
|
1321
|
+
# Grating-style x/y Bloch + z PML: standard boundary treatment on z keeps
|
|
1322
|
+
# the outer plane exactly zero (mirrored by the adjoint replay).
|
|
1323
|
+
update_electric_fields_bloch_xy_standard_z(solver)
|
|
1324
|
+
_apply_electric_z_cpml_corrections(solver, solver.Ex, solver.Ey, solver.Hx, solver.Hy, imag=False)
|
|
1325
|
+
_apply_electric_z_cpml_corrections(
|
|
1326
|
+
solver,
|
|
1327
|
+
solver.Ex_imag,
|
|
1328
|
+
solver.Ey_imag,
|
|
1329
|
+
solver.Hx_imag,
|
|
1330
|
+
solver.Hy_imag,
|
|
1331
|
+
imag=True,
|
|
1332
|
+
)
|
|
1333
|
+
return
|
|
1334
|
+
# General single-PML-axis permutation (x or y): the base full-Bloch update
|
|
1335
|
+
# carries the true wrap phase on both periodic axes and the natural unit phase
|
|
1336
|
+
# on the absorbing axis, then the recursive-convolution stretch is folded in
|
|
1337
|
+
# along that axis for the two components with an absorbing-axis derivative.
|
|
1338
|
+
update_electric_fields_bloch(solver)
|
|
1339
|
+
if pml_axis == "y":
|
|
1340
|
+
_apply_electric_y_cpml_corrections(solver, solver.Ex, solver.Ez, solver.Hx, solver.Hz, imag=False)
|
|
1341
|
+
_apply_electric_y_cpml_corrections(
|
|
1342
|
+
solver, solver.Ex_imag, solver.Ez_imag, solver.Hx_imag, solver.Hz_imag, imag=True
|
|
1343
|
+
)
|
|
1344
|
+
else:
|
|
1345
|
+
_apply_electric_x_cpml_corrections(solver, solver.Ey, solver.Ez, solver.Hy, solver.Hz, imag=False)
|
|
1346
|
+
_apply_electric_x_cpml_corrections(
|
|
1347
|
+
solver, solver.Ey_imag, solver.Ez_imag, solver.Hy_imag, solver.Hz_imag, imag=True
|
|
1348
|
+
)
|
|
1349
|
+
|
|
1350
|
+
|
|
1351
|
+
def clamp_field_face(solver, field, axis, side):
|
|
1352
|
+
side_code = 0 if side == "low" else 1
|
|
1353
|
+
module = getattr(solver, "fdtd_module", None)
|
|
1354
|
+
if module is not None and module.__class__.__name__ == "NativeFDTDModule":
|
|
1355
|
+
module.clampFieldFace3D(field=field, axis=int(axis), side=side_code).launchRaw()
|
|
1356
|
+
return
|
|
1357
|
+
index = 0 if side_code == 0 else field.shape[axis] - 1
|
|
1358
|
+
field.select(axis, index).zero_()
|
|
1359
|
+
|
|
1360
|
+
|
|
1361
|
+
def _launch_native_clamp(solver, kernel_name, **kwargs):
|
|
1362
|
+
module = getattr(solver, "fdtd_module", None)
|
|
1363
|
+
if module is None or module.__class__.__name__ != "NativeFDTDModule":
|
|
1364
|
+
return False
|
|
1365
|
+
getattr(module, kernel_name)(**kwargs).launchRaw()
|
|
1366
|
+
return True
|
|
1367
|
+
|
|
1368
|
+
|
|
1369
|
+
def _enforce_native_pec_field(solver, field, axis_a, low_a, high_a, axis_b, low_b, high_b):
|
|
1370
|
+
sides_a = (low_a == BOUNDARY_PEC, high_a == BOUNDARY_PEC)
|
|
1371
|
+
sides_b = (low_b == BOUNDARY_PEC, high_b == BOUNDARY_PEC)
|
|
1372
|
+
if not any(sides_a) and not any(sides_b):
|
|
1373
|
+
return True
|
|
1374
|
+
|
|
1375
|
+
if all(sides_a) and all(sides_b):
|
|
1376
|
+
return _launch_native_clamp(solver, "clampPecBoundary3D", field=field, axisA=axis_a, axisB=axis_b)
|
|
1377
|
+
if all(sides_a):
|
|
1378
|
+
if not _launch_native_clamp(solver, "clampPecBoundary3D", field=field, axisA=axis_a, axisB=axis_a):
|
|
1379
|
+
return False
|
|
1380
|
+
else:
|
|
1381
|
+
for side_code, is_pec in enumerate(sides_a):
|
|
1382
|
+
if is_pec and not _launch_native_clamp(
|
|
1383
|
+
solver, "clampFieldFace3D", field=field, axis=axis_a, side=side_code
|
|
1384
|
+
):
|
|
1385
|
+
return False
|
|
1386
|
+
|
|
1387
|
+
if all(sides_b):
|
|
1388
|
+
return _launch_native_clamp(solver, "clampPecBoundary3D", field=field, axisA=axis_b, axisB=axis_b)
|
|
1389
|
+
for side_code, is_pec in enumerate(sides_b):
|
|
1390
|
+
if is_pec and not _launch_native_clamp(solver, "clampFieldFace3D", field=field, axis=axis_b, side=side_code):
|
|
1391
|
+
return False
|
|
1392
|
+
return True
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
def enforce_pec_boundaries(solver):
|
|
1396
|
+
if not getattr(solver, "has_pec_faces", False):
|
|
1397
|
+
return
|
|
1398
|
+
|
|
1399
|
+
if _enforce_native_pec_field(
|
|
1400
|
+
solver,
|
|
1401
|
+
solver.Ex,
|
|
1402
|
+
1,
|
|
1403
|
+
solver.boundary_y_low_code,
|
|
1404
|
+
solver.boundary_y_high_code,
|
|
1405
|
+
2,
|
|
1406
|
+
solver.boundary_z_low_code,
|
|
1407
|
+
solver.boundary_z_high_code,
|
|
1408
|
+
) and _enforce_native_pec_field(
|
|
1409
|
+
solver,
|
|
1410
|
+
solver.Ey,
|
|
1411
|
+
0,
|
|
1412
|
+
solver.boundary_x_low_code,
|
|
1413
|
+
solver.boundary_x_high_code,
|
|
1414
|
+
2,
|
|
1415
|
+
solver.boundary_z_low_code,
|
|
1416
|
+
solver.boundary_z_high_code,
|
|
1417
|
+
) and _enforce_native_pec_field(
|
|
1418
|
+
solver,
|
|
1419
|
+
solver.Ez,
|
|
1420
|
+
0,
|
|
1421
|
+
solver.boundary_x_low_code,
|
|
1422
|
+
solver.boundary_x_high_code,
|
|
1423
|
+
1,
|
|
1424
|
+
solver.boundary_y_low_code,
|
|
1425
|
+
solver.boundary_y_high_code,
|
|
1426
|
+
):
|
|
1427
|
+
return
|
|
1428
|
+
|
|
1429
|
+
face_specs = (
|
|
1430
|
+
(solver.boundary_x_low_code, "low", (("Ey", 0), ("Ez", 0))),
|
|
1431
|
+
(solver.boundary_x_high_code, "high", (("Ey", 0), ("Ez", 0))),
|
|
1432
|
+
(solver.boundary_y_low_code, "low", (("Ex", 1), ("Ez", 1))),
|
|
1433
|
+
(solver.boundary_y_high_code, "high", (("Ex", 1), ("Ez", 1))),
|
|
1434
|
+
(solver.boundary_z_low_code, "low", (("Ex", 2), ("Ey", 2))),
|
|
1435
|
+
(solver.boundary_z_high_code, "high", (("Ex", 2), ("Ey", 2))),
|
|
1436
|
+
)
|
|
1437
|
+
for boundary_code, side, targets in face_specs:
|
|
1438
|
+
if boundary_code != BOUNDARY_PEC:
|
|
1439
|
+
continue
|
|
1440
|
+
for field_name, axis in targets:
|
|
1441
|
+
clamp_field_face(solver, getattr(solver, field_name), axis, side)
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
def apply_sibc_surface(solver):
|
|
1445
|
+
"""Override the tangential E on a ``LossyMetalMedium`` surface via the Leontovich
|
|
1446
|
+
surface-impedance relation ``E_t = Zs * (n_hat x H)`` (passive/absorbing branch).
|
|
1447
|
+
|
|
1448
|
+
The surface impedance is a narrowband series R-L, ``Zs(omega0) = R + j*omega0*Ls``,
|
|
1449
|
+
so in the time domain each tangential face reads the vacuum-side tangential H
|
|
1450
|
+
(index-aligned on the Yee grid by ``_configure_sibc``) and forms
|
|
1451
|
+
``E_surface = sign * (R * H + Ls * dH/dt)``, storing the previous H for the
|
|
1452
|
+
surface-inductance dH/dt term. The metal interior is masked to zero by the
|
|
1453
|
+
coefficient setup, so the surface acts as a semi-infinite good-conductor
|
|
1454
|
+
termination at roughly a skin-depth-free (>=10x coarser) cell size.
|
|
1455
|
+
"""
|
|
1456
|
+
state = getattr(solver, "_sibc", None)
|
|
1457
|
+
if state is None:
|
|
1458
|
+
return
|
|
1459
|
+
surface_r = state["surface_r"]
|
|
1460
|
+
surface_l_over_dt = state["surface_l"] / solver.dt
|
|
1461
|
+
for face in state["faces"]:
|
|
1462
|
+
solver.fdtd_module.applySibcSurface3D(
|
|
1463
|
+
electric=getattr(solver, face["e_name"]),
|
|
1464
|
+
magnetic=getattr(solver, face["h_name"]),
|
|
1465
|
+
axis=face["axis"],
|
|
1466
|
+
electricIndex=face["electric_index"],
|
|
1467
|
+
magneticIndex=face["magnetic_index"],
|
|
1468
|
+
sign=face["sign"],
|
|
1469
|
+
surfaceR=surface_r,
|
|
1470
|
+
surfaceLOverDt=surface_l_over_dt,
|
|
1471
|
+
hPrev=face["h_prev"],
|
|
1472
|
+
).launchRaw()
|
|
1473
|
+
|
|
1474
|
+
|
|
1475
|
+
def clamp_pec_boundaries(solver):
|
|
1476
|
+
enforce_pec_boundaries(solver)
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
def apply_mur_boundaries(solver):
|
|
1480
|
+
# First-order Mur absorbing boundary on the CUDA E-field tensors after the
|
|
1481
|
+
# interior E-update. For an outer face:
|
|
1482
|
+
# E_boundary(n+1) = E_adjacent(n) + coef * (E_adjacent(n+1) - E_boundary(n))
|
|
1483
|
+
# with coef = (c*dt - d) / (c*dt + d) and d the normal cell size. Each face is
|
|
1484
|
+
# a single native-CUDA kernel launch that updates its persistent boundary /
|
|
1485
|
+
# first-interior plane buffers in place, so it stays capturable into the tail
|
|
1486
|
+
# CUDA graph and carries no per-step host arithmetic or allocation.
|
|
1487
|
+
if not getattr(solver, "has_mur_faces", False):
|
|
1488
|
+
return
|
|
1489
|
+
|
|
1490
|
+
for entry in solver._mur_state:
|
|
1491
|
+
solver.fdtd_module.applyMurBoundary3D(
|
|
1492
|
+
field=getattr(solver, entry["field"]),
|
|
1493
|
+
axis=entry["axis"],
|
|
1494
|
+
boundaryIndex=entry["boundary_index"],
|
|
1495
|
+
adjacentIndex=entry["adjacent_index"],
|
|
1496
|
+
coef=entry["coef"],
|
|
1497
|
+
prevBoundary=entry["prev_boundary"],
|
|
1498
|
+
prevAdjacent=entry["prev_adjacent"],
|
|
1499
|
+
).launchRaw()
|
|
1500
|
+
|
|
1501
|
+
|
|
1502
|
+
def init_field(solver):
|
|
1503
|
+
solver.Ex = torch.zeros((solver.Nx - 1, solver.Ny, solver.Nz), device=solver.device, dtype=torch.float32)
|
|
1504
|
+
solver.Ey = torch.zeros((solver.Nx, solver.Ny - 1, solver.Nz), device=solver.device, dtype=torch.float32)
|
|
1505
|
+
solver.Ez = torch.zeros((solver.Nx, solver.Ny, solver.Nz - 1), device=solver.device, dtype=torch.float32)
|
|
1506
|
+
solver.Hx = torch.zeros((solver.Nx, solver.Ny - 1, solver.Nz - 1), device=solver.device, dtype=torch.float32)
|
|
1507
|
+
solver.Hy = torch.zeros((solver.Nx - 1, solver.Ny, solver.Nz - 1), device=solver.device, dtype=torch.float32)
|
|
1508
|
+
solver.Hz = torch.zeros((solver.Nx - 1, solver.Ny - 1, solver.Nz), device=solver.device, dtype=torch.float32)
|
|
1509
|
+
|
|
1510
|
+
solver.build_materials(solver.scene)
|
|
1511
|
+
initialize_boundary_state(solver)
|
|
1512
|
+
solver._build_update_coefficients()
|
|
1513
|
+
solver._initialize_dispersive_state()
|
|
1514
|
+
solver._initialize_magnetic_dispersive_state()
|
|
1515
|
+
initialize_tfsf_state(solver)
|
|
1516
|
+
initialize_source_terms(solver)
|
|
1517
|
+
|
|
1518
|
+
|
|
1519
|
+
def _compute_shutoff_min_step(solver, shutoff_check_interval: int) -> int:
|
|
1520
|
+
import math
|
|
1521
|
+
|
|
1522
|
+
source_time = getattr(solver, "_source_time", None)
|
|
1523
|
+
settling_time = 0.0
|
|
1524
|
+
if source_time is not None:
|
|
1525
|
+
if isinstance(source_time, dict):
|
|
1526
|
+
settling_time = float(source_time.get("settling_time", 0.0))
|
|
1527
|
+
else:
|
|
1528
|
+
settling_time = float(getattr(source_time, "settling_time", 0.0))
|
|
1529
|
+
settling_step = int(math.ceil(settling_time / solver.dt)) if settling_time > 0.0 else 0
|
|
1530
|
+
|
|
1531
|
+
dft_start_step = getattr(solver, "dft_start_step", None)
|
|
1532
|
+
|
|
1533
|
+
observer_entries = getattr(solver, "_observer_spectral_entries", None)
|
|
1534
|
+
observer_start = 0
|
|
1535
|
+
if observer_entries:
|
|
1536
|
+
observer_start = min(int(entry["start_step"]) for entry in observer_entries)
|
|
1537
|
+
|
|
1538
|
+
floor = max(settling_step, dft_start_step or 0, observer_start or 0)
|
|
1539
|
+
return floor + 2 * shutoff_check_interval
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
def _electric_field_energy(solver) -> torch.Tensor:
|
|
1543
|
+
energy = (
|
|
1544
|
+
(solver.eps_Ex * solver.Ex * solver.Ex).sum()
|
|
1545
|
+
+ (solver.eps_Ey * solver.Ey * solver.Ey).sum()
|
|
1546
|
+
+ (solver.eps_Ez * solver.Ez * solver.Ez).sum()
|
|
1547
|
+
)
|
|
1548
|
+
if has_complex_fields(solver):
|
|
1549
|
+
energy = (
|
|
1550
|
+
energy
|
|
1551
|
+
+ (solver.eps_Ex * solver.Ex_imag * solver.Ex_imag).sum()
|
|
1552
|
+
+ (solver.eps_Ey * solver.Ey_imag * solver.Ey_imag).sum()
|
|
1553
|
+
+ (solver.eps_Ez * solver.Ez_imag * solver.Ez_imag).sum()
|
|
1554
|
+
)
|
|
1555
|
+
return energy
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
def _planned_window_normalization(window_type: str, start_step: int, end_step: int) -> float:
|
|
1559
|
+
# Sum the spectral window weights over the full planned [start_step, end_step)
|
|
1560
|
+
# range, matching compute_window_weight / accumulate_dft exactly for a full run.
|
|
1561
|
+
total = int(end_step) - int(start_step)
|
|
1562
|
+
if total <= 0:
|
|
1563
|
+
return 0.0
|
|
1564
|
+
if window_type == "none":
|
|
1565
|
+
return float(total)
|
|
1566
|
+
positions = np.arange(total, dtype=np.float64) / total
|
|
1567
|
+
if window_type == "hanning":
|
|
1568
|
+
weights = 0.5 * (1.0 - np.cos(2.0 * np.pi * positions))
|
|
1569
|
+
return float(weights.sum())
|
|
1570
|
+
if window_type == "ramp":
|
|
1571
|
+
ramp_fraction = 0.1
|
|
1572
|
+
weights = np.ones_like(positions)
|
|
1573
|
+
ramp_mask = positions < ramp_fraction
|
|
1574
|
+
weights[ramp_mask] = 0.5 * (1.0 - np.cos(np.pi * positions[ramp_mask] / ramp_fraction))
|
|
1575
|
+
return float(weights.sum())
|
|
1576
|
+
return float(total)
|
|
1577
|
+
|
|
1578
|
+
|
|
1579
|
+
def _complete_spectral_normalization(solver, time_steps: int) -> None:
|
|
1580
|
+
# After an early auto-shutoff the omitted tail steps carry negligible field, so
|
|
1581
|
+
# the running-DFT / observer numerators are already complete. Restore each
|
|
1582
|
+
# spectral normalizer to its planned full-window value so the
|
|
1583
|
+
# 2 / window_normalization scale is not inflated by the shortened run.
|
|
1584
|
+
if getattr(solver, "dft_enabled", False) and getattr(solver, "_dft_entries", None):
|
|
1585
|
+
for index in range(len(solver._dft_entries)):
|
|
1586
|
+
start_step = int(solver._dft_start_steps[index])
|
|
1587
|
+
end_step = int(solver._dft_end_steps[index])
|
|
1588
|
+
if end_step < 0:
|
|
1589
|
+
end_step = int(time_steps)
|
|
1590
|
+
solver._dft_window_normalization_values[index] = _planned_window_normalization(
|
|
1591
|
+
solver.dft_window_type, start_step, end_step
|
|
1592
|
+
)
|
|
1593
|
+
if getattr(solver, "observers_enabled", False):
|
|
1594
|
+
for entry in solver._observer_spectral_entries:
|
|
1595
|
+
end_step = entry["end_step"] if entry["end_step"] is not None else int(time_steps)
|
|
1596
|
+
entry["window_normalization"] = _planned_window_normalization(
|
|
1597
|
+
solver.observer_window_type, int(entry["start_step"]), int(end_step)
|
|
1598
|
+
)
|
|
1599
|
+
|
|
1600
|
+
|
|
1601
|
+
def _field_update_block(solver, time_value):
|
|
1602
|
+
"""One step of the time-marching field-update core: the magnetic and electric
|
|
1603
|
+
Yee updates plus their in-place dispersive/Kerr/CPML/Bloch state advances.
|
|
1604
|
+
|
|
1605
|
+
This is exactly the contiguous, time-marching part of the step that carries no
|
|
1606
|
+
per-step host input when TFSF and magnetic sources are absent, so it can be
|
|
1607
|
+
captured once into a CUDA graph and replayed (additive electric sources and
|
|
1608
|
+
the running DFT stay outside the graph; native SIBC remains inside). Kept as
|
|
1609
|
+
a single function so the normal and graph-captured paths execute an identical
|
|
1610
|
+
kernel sequence.
|
|
1611
|
+
"""
|
|
1612
|
+
if getattr(solver, "modulation_enabled", False):
|
|
1613
|
+
solver.fdtd_module.advanceModulationTime3D(
|
|
1614
|
+
ModulationTime=solver._modulation_time,
|
|
1615
|
+
dt=solver.dt,
|
|
1616
|
+
).launchRaw()
|
|
1617
|
+
solver._advance_magnetic_dispersive_state()
|
|
1618
|
+
update_magnetic_fields(solver, solver.Hx, solver.Hy, solver.Hz, solver.Ex, solver.Ey, solver.Ez)
|
|
1619
|
+
if has_complex_fields(solver):
|
|
1620
|
+
update_magnetic_fields(
|
|
1621
|
+
solver,
|
|
1622
|
+
solver.Hx_imag,
|
|
1623
|
+
solver.Hy_imag,
|
|
1624
|
+
solver.Hz_imag,
|
|
1625
|
+
solver.Ex_imag,
|
|
1626
|
+
solver.Ey_imag,
|
|
1627
|
+
solver.Ez_imag,
|
|
1628
|
+
imag=True,
|
|
1629
|
+
)
|
|
1630
|
+
if solver.tfsf_enabled:
|
|
1631
|
+
apply_tfsf_h_correction(solver, time_value)
|
|
1632
|
+
advance_tfsf_auxiliary_magnetic(solver)
|
|
1633
|
+
if solver._magnetic_source_terms:
|
|
1634
|
+
inject_magnetic_surface_source_terms(solver, time_value=time_value)
|
|
1635
|
+
solver._apply_magnetic_dispersive_corrections()
|
|
1636
|
+
|
|
1637
|
+
if has_complex_fields(solver):
|
|
1638
|
+
solver._advance_dispersive_state()
|
|
1639
|
+
if solver.uses_cpml:
|
|
1640
|
+
update_electric_fields_bloch_cpml(solver)
|
|
1641
|
+
else:
|
|
1642
|
+
update_electric_fields_bloch(solver)
|
|
1643
|
+
else:
|
|
1644
|
+
solver._advance_dispersive_state()
|
|
1645
|
+
if solver.nonlinear_enabled:
|
|
1646
|
+
solver._update_nonlinear_electric_coefficients()
|
|
1647
|
+
# Snapshot sigma . E^n before the base update overwrites E (no-op unless the
|
|
1648
|
+
# medium is both full-anisotropic and conductive).
|
|
1649
|
+
capture_aniso_conduction_currents(solver)
|
|
1650
|
+
update_electric_fields(
|
|
1651
|
+
solver,
|
|
1652
|
+
solver.Ex,
|
|
1653
|
+
solver.Ey,
|
|
1654
|
+
solver.Ez,
|
|
1655
|
+
solver.Hx,
|
|
1656
|
+
solver.Hy,
|
|
1657
|
+
solver.Hz,
|
|
1658
|
+
time_value=time_value,
|
|
1659
|
+
)
|
|
1660
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
1661
|
+
apply_full_aniso_corrections(solver)
|
|
1662
|
+
apply_full_aniso_conduction(solver)
|
|
1663
|
+
apply_sibc_surface(solver)
|
|
1664
|
+
|
|
1665
|
+
|
|
1666
|
+
def _make_field_update_runner(solver, use_cuda_graph: bool):
|
|
1667
|
+
"""Return ``run(time_value)`` for the field-update block, capturing it into a
|
|
1668
|
+
CUDA graph when requested and safe. Falls back to the direct call for TFSF /
|
|
1669
|
+
magnetic-source scenes (whose block carries per-step host input) or if capture
|
|
1670
|
+
is unavailable. Capture happens on the zero initial field, a fixed point of
|
|
1671
|
+
the source-free block, so it does not perturb the physical run.
|
|
1672
|
+
"""
|
|
1673
|
+
solver._cuda_graph_active = False
|
|
1674
|
+
|
|
1675
|
+
def normal(time_value):
|
|
1676
|
+
_field_update_block(solver, time_value)
|
|
1677
|
+
|
|
1678
|
+
# v1 scope: the standard real-field path (optionally conductive), linear
|
|
1679
|
+
# electric/magnetic dispersion, the instantaneous nonlinear family (Kerr chi3,
|
|
1680
|
+
# chi2, and two-photon-absorption loss), the complex split-field Bloch path
|
|
1681
|
+
# (optionally with a single CPML axis), and reference-provider TFSF injection
|
|
1682
|
+
# (axis-aligned / x-Ez plane waves, in both the six-face box and the two-face
|
|
1683
|
+
# non-periodic slab injection mode). Magnetic surface sources put a
|
|
1684
|
+
# host-evaluated signal scalar inside the block, so those scenes stay on the
|
|
1685
|
+
# eager path.
|
|
1686
|
+
#
|
|
1687
|
+
# A reference-provider TFSF scene is safe to capture: its in-block H correction
|
|
1688
|
+
# and auxiliary magnetic advance read the incident field from the
|
|
1689
|
+
# device-resident auxiliary 1D grid (aux.electric / aux.magnetic) at fixed
|
|
1690
|
+
# integer sample indices, so the block carries no per-step host input, and the
|
|
1691
|
+
# reference injection kernel's warp-aggregated writes are collision-free for the
|
|
1692
|
+
# axis-aligned face layout (bit-reproducible run to run). The 1D source waveform
|
|
1693
|
+
# is evaluated on the host only in the E-side auxiliary advance, which runs
|
|
1694
|
+
# eagerly after the block (outside the captured region), and the captured H
|
|
1695
|
+
# correction reads whatever aux.electric holds at replay -- exactly matching the
|
|
1696
|
+
# eager path. The block mutates only aux.magnetic among the auxiliary tensors;
|
|
1697
|
+
# it is snapshotted below alongside aux.electric so warmup/capture on the zero
|
|
1698
|
+
# field leave the incident line untouched for the physical run, and the incident
|
|
1699
|
+
# line is a zero fixed point during capture (aux.electric stays zero until the
|
|
1700
|
+
# eager E-side advance injects the source after capture). The oblique
|
|
1701
|
+
# auxiliary-line provider is excluded (its interpolated injection kernel is not
|
|
1702
|
+
# bit-reproducible under cross-warp atomics), and the oblique-CW / analytic
|
|
1703
|
+
# providers evaluate the waveform on the host each step; all stay eager
|
|
1704
|
+
# (see excitation.tfsf_incident_is_gpu_driven).
|
|
1705
|
+
#
|
|
1706
|
+
# Linear dispersion is safe to capture: the Debye/Drude/Lorentz ADE advance and
|
|
1707
|
+
# the polarization-current subtraction launch persistent per-pole state tensors
|
|
1708
|
+
# (polarization/current, allocated once at init) with fixed dt/decay/restoring
|
|
1709
|
+
# scalars, so they carry no per-step host input. The ADE is linear with zero
|
|
1710
|
+
# forcing at E = H = 0, so with all pole state starting at zero the zero initial
|
|
1711
|
+
# field is still a fixed point of the extended block; warmup/capture leaves the
|
|
1712
|
+
# pole state at zero and does not perturb the physical run.
|
|
1713
|
+
#
|
|
1714
|
+
# Instantaneous nonlinearity is safe to capture too: the Kerr curl-only kernel
|
|
1715
|
+
# and the general chi2/TPA coefficient kernel recompute the dynamic curl/decay
|
|
1716
|
+
# tensors in place from the current field, the persistent chi3/chi2/tpa maps,
|
|
1717
|
+
# and fixed dt/eps0 scalars, with no per-step host input. Unlike dispersion they
|
|
1718
|
+
# carry no accumulated auxiliary state: the dynamic coefficients are a pure
|
|
1719
|
+
# function of the live field, rewritten before they are read every step, so the
|
|
1720
|
+
# fields+psi snapshot already covers everything the block mutates. Every
|
|
1721
|
+
# nonlinear polarization vanishes at E = 0 (P ~ chi2 E^2, chi3 |E|^2 E, and
|
|
1722
|
+
# sigma_NL ~ |E|^2), collapsing the dynamic coefficients to their linear values,
|
|
1723
|
+
# so the zero field remains a fixed point through warmup/capture.
|
|
1724
|
+
#
|
|
1725
|
+
# The complex (split-field Bloch) path is safe to capture too: a Bloch-periodic
|
|
1726
|
+
# scene marches a second real FDTD copy (the imaginary split field) through the
|
|
1727
|
+
# same magnetic/electric curl+decay kernels, coupled only by a fixed per-axis
|
|
1728
|
+
# wrap phase (cos/sin of the Bloch wavevector, constant for the whole run), so
|
|
1729
|
+
# the block carries no per-step host input. The extra state it evolves -- the
|
|
1730
|
+
# imaginary E/H fields and their CPML psi memory -- is snapshotted alongside the
|
|
1731
|
+
# real fields below (the imaginary psi tensors already match the ``psi`` prefix),
|
|
1732
|
+
# and the split update is linear with zero forcing at E = H = 0, so the zero
|
|
1733
|
+
# field (real and imaginary) stays a fixed point through warmup/capture. The
|
|
1734
|
+
# complex running DFT keeps the host tail (build_dft_step_tables declines the GPU
|
|
1735
|
+
# weight table for complex fields), so only the field-update block is captured.
|
|
1736
|
+
graphable = (
|
|
1737
|
+
use_cuda_graph
|
|
1738
|
+
and torch.cuda.is_available()
|
|
1739
|
+
# TFSF is capturable only for the reference providers, whose in-block
|
|
1740
|
+
# correction reads the device-resident incident line through the
|
|
1741
|
+
# bit-reproducible integer-indexed reference kernel and carries no per-step
|
|
1742
|
+
# host input (see _make_field_update_runner docstring above).
|
|
1743
|
+
and (not solver.tfsf_enabled or tfsf_incident_is_gpu_driven(solver))
|
|
1744
|
+
# A magnetic surface source injects a host-evaluated signal scalar inside
|
|
1745
|
+
# the block, which a captured graph would freeze at its capture-time value.
|
|
1746
|
+
and not solver._magnetic_source_terms
|
|
1747
|
+
)
|
|
1748
|
+
if not graphable:
|
|
1749
|
+
return normal
|
|
1750
|
+
from ..cuda.runtime.graph import CudaGraphRunner
|
|
1751
|
+
|
|
1752
|
+
# Snapshot the state this block mutates (real + imaginary fields, CPML psi) so
|
|
1753
|
+
# warmup/capture do not perturb the physical run, then restore before stepping
|
|
1754
|
+
# begins. The imaginary split fields exist only on the complex Bloch path; on
|
|
1755
|
+
# the real path they are absent from ``vars(solver)`` and drop out naturally.
|
|
1756
|
+
snapshot_field_names = (
|
|
1757
|
+
"Ex", "Ey", "Ez", "Hx", "Hy", "Hz",
|
|
1758
|
+
"Ex_imag", "Ey_imag", "Ez_imag", "Hx_imag", "Hy_imag", "Hz_imag",
|
|
1759
|
+
)
|
|
1760
|
+
state = {
|
|
1761
|
+
k: v
|
|
1762
|
+
for k, v in vars(solver).items()
|
|
1763
|
+
if isinstance(v, torch.Tensor)
|
|
1764
|
+
and (k in snapshot_field_names or k.startswith("psi") or k == "_modulation_time")
|
|
1765
|
+
}
|
|
1766
|
+
# The TFSF field-update block advances the auxiliary 1D magnetic grid in place
|
|
1767
|
+
# (and reads the auxiliary electric grid). Snapshot both auxiliary tensors so
|
|
1768
|
+
# warmup/capture on the zero field leave the incident line exactly as the
|
|
1769
|
+
# physical run expects; the "psi" / field filter above does not reach them
|
|
1770
|
+
# because they live on the auxiliary-grid object, not on ``vars(solver)``.
|
|
1771
|
+
if getattr(solver, "tfsf_enabled", False) and getattr(solver, "_tfsf_state", None):
|
|
1772
|
+
aux = solver._tfsf_state.get("auxiliary_grid")
|
|
1773
|
+
if aux is not None:
|
|
1774
|
+
state["_tfsf_aux_electric"] = aux.electric
|
|
1775
|
+
state["_tfsf_aux_magnetic"] = aux.magnetic
|
|
1776
|
+
sibc_state = getattr(solver, "_sibc", None)
|
|
1777
|
+
if sibc_state is not None:
|
|
1778
|
+
for index, face in enumerate(sibc_state["faces"]):
|
|
1779
|
+
state[f"_sibc_h_prev_{index}"] = face["h_prev"]
|
|
1780
|
+
saved = {k: v.clone() for k, v in state.items()}
|
|
1781
|
+
|
|
1782
|
+
def _restore():
|
|
1783
|
+
for k, v in saved.items():
|
|
1784
|
+
state[k].copy_(v)
|
|
1785
|
+
|
|
1786
|
+
try:
|
|
1787
|
+
runner = CudaGraphRunner(enabled=True, warmup_steps=3)
|
|
1788
|
+
replay = runner.capture(lambda: _field_update_block(solver, 0.0))
|
|
1789
|
+
except Exception:
|
|
1790
|
+
# Any capture failure (e.g. a non-capturable kernel) degrades to the
|
|
1791
|
+
# normal path rather than breaking the solve.
|
|
1792
|
+
_restore()
|
|
1793
|
+
return normal
|
|
1794
|
+
_restore()
|
|
1795
|
+
solver._cuda_graph_active = True
|
|
1796
|
+
return lambda time_value: replay()
|
|
1797
|
+
|
|
1798
|
+
|
|
1799
|
+
def _post_source_block(solver):
|
|
1800
|
+
"""The time-independent tail after source injection: dispersive corrections,
|
|
1801
|
+
PEC clamp, Mur ABC, and the GPU-driven running-DFT accumulation. Captured as
|
|
1802
|
+
a second graph so its kernel launches collapse to one replay; only valid when
|
|
1803
|
+
the DFT is GPU-driven (``build_dft_step_tables`` succeeded)."""
|
|
1804
|
+
solver._apply_dispersive_corrections()
|
|
1805
|
+
if not solver.tfsf_enabled:
|
|
1806
|
+
enforce_pec_boundaries(solver)
|
|
1807
|
+
apply_mur_boundaries(solver)
|
|
1808
|
+
solver.accumulate_dft_gpu()
|
|
1809
|
+
|
|
1810
|
+
|
|
1811
|
+
def _make_tail_runner(solver, use_gpu_dft: bool):
|
|
1812
|
+
"""Capture the post-source tail (PEC + Mur + GPU-DFT) into a graph. Returns a
|
|
1813
|
+
replay callable, or ``None`` to keep the inline tail. Snapshots the state the
|
|
1814
|
+
tail mutates (fields, the device step counter, the DFT accumulators) around
|
|
1815
|
+
capture so warmup/capture on the zero field do not perturb the run."""
|
|
1816
|
+
if not (use_gpu_dft and getattr(solver, "_cuda_graph_active", False)):
|
|
1817
|
+
return None
|
|
1818
|
+
from ..cuda.runtime.graph import CudaGraphRunner
|
|
1819
|
+
|
|
1820
|
+
state = {}
|
|
1821
|
+
for name in ("Ex", "Ey", "Ez", "Hx", "Hy", "Hz"):
|
|
1822
|
+
tensor = getattr(solver, name, None)
|
|
1823
|
+
if isinstance(tensor, torch.Tensor):
|
|
1824
|
+
state[("field", name)] = tensor
|
|
1825
|
+
if isinstance(getattr(solver, "_dft_step", None), torch.Tensor):
|
|
1826
|
+
state[("step",)] = solver._dft_step
|
|
1827
|
+
for comp, buffers in getattr(solver, "_dft_batched_fields", {}).items():
|
|
1828
|
+
for key in ("real", "imag"):
|
|
1829
|
+
tensor = buffers.get(key)
|
|
1830
|
+
if isinstance(tensor, torch.Tensor):
|
|
1831
|
+
state[("dft", comp, key)] = tensor
|
|
1832
|
+
saved = {k: v.clone() for k, v in state.items()}
|
|
1833
|
+
|
|
1834
|
+
def _restore():
|
|
1835
|
+
for k, v in saved.items():
|
|
1836
|
+
state[k].copy_(v)
|
|
1837
|
+
|
|
1838
|
+
try:
|
|
1839
|
+
replay = CudaGraphRunner(enabled=True, warmup_steps=3).capture(
|
|
1840
|
+
lambda: _post_source_block(solver)
|
|
1841
|
+
)
|
|
1842
|
+
except Exception:
|
|
1843
|
+
_restore()
|
|
1844
|
+
return None
|
|
1845
|
+
_restore()
|
|
1846
|
+
return replay
|
|
1847
|
+
|
|
1848
|
+
|
|
1849
|
+
def solve(
|
|
1850
|
+
solver,
|
|
1851
|
+
time_steps: int,
|
|
1852
|
+
dft_frequency: float = None,
|
|
1853
|
+
enable_plot: bool = False,
|
|
1854
|
+
dft_window: str = "hanning",
|
|
1855
|
+
full_field_dft: bool = True,
|
|
1856
|
+
normalize_source: bool = False,
|
|
1857
|
+
shutoff: float = 0.0,
|
|
1858
|
+
shutoff_check_interval: int = 100,
|
|
1859
|
+
use_cuda_graph: bool = False,
|
|
1860
|
+
):
|
|
1861
|
+
if solver.verbose:
|
|
1862
|
+
print(f"Starting 3D FDTD simulation (Yee grid), grid size: {solver.Nx}x{solver.Ny}x{solver.Nz}")
|
|
1863
|
+
if normalize_source and len(getattr(solver, "_compiled_sources", ())) != 1:
|
|
1864
|
+
raise NotImplementedError("normalize_source currently requires exactly one compiled source.")
|
|
1865
|
+
solver._normalize_source = normalize_source
|
|
1866
|
+
solver._synchronize_device()
|
|
1867
|
+
solve_start = time.perf_counter()
|
|
1868
|
+
|
|
1869
|
+
if dft_frequency is not None and full_field_dft:
|
|
1870
|
+
solver.enable_dft(dft_frequency, window_type=dft_window, end_step=time_steps)
|
|
1871
|
+
else:
|
|
1872
|
+
solver.dft_enabled = False
|
|
1873
|
+
solver._dft_entries = []
|
|
1874
|
+
solver._sync_dft_legacy_state()
|
|
1875
|
+
|
|
1876
|
+
observer_frequency = dft_frequency if dft_frequency is not None else solver.source_frequency
|
|
1877
|
+
if solver.observers:
|
|
1878
|
+
solver._prepare_observers(observer_frequency, dft_window, time_steps)
|
|
1879
|
+
if getattr(solver, "time_observers", None):
|
|
1880
|
+
solver._prepare_time_observers(time_steps)
|
|
1881
|
+
|
|
1882
|
+
solver._shutoff_triggered = False
|
|
1883
|
+
solver._shutoff_step = None
|
|
1884
|
+
solver._shutoff_peak = torch.zeros((), device=solver.device, dtype=solver.Ex.dtype)
|
|
1885
|
+
shutoff_min_step = _compute_shutoff_min_step(solver, shutoff_check_interval)
|
|
1886
|
+
|
|
1887
|
+
run_field_update = _make_field_update_runner(solver, use_cuda_graph)
|
|
1888
|
+
|
|
1889
|
+
# When the field-update graph is active, drive the running DFT from a
|
|
1890
|
+
# precomputed GPU weight table indexed by a device step counter, dropping the
|
|
1891
|
+
# per-step host arithmetic and host->device transfer of the DFT weights.
|
|
1892
|
+
use_gpu_dft = False
|
|
1893
|
+
if getattr(solver, "_cuda_graph_active", False) and getattr(solver, "dft_enabled", False):
|
|
1894
|
+
use_gpu_dft = solver.build_dft_step_tables(time_steps)
|
|
1895
|
+
run_tail = _make_tail_runner(solver, use_gpu_dft)
|
|
1896
|
+
solver._tail_graph_active = run_tail is not None
|
|
1897
|
+
|
|
1898
|
+
iterator = range(time_steps)
|
|
1899
|
+
pbar = None
|
|
1900
|
+
if solver.verbose:
|
|
1901
|
+
pbar = tqdm(
|
|
1902
|
+
iterator,
|
|
1903
|
+
desc="FDTD",
|
|
1904
|
+
unit="step",
|
|
1905
|
+
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, {rate_fmt}{postfix}]",
|
|
1906
|
+
)
|
|
1907
|
+
iterator = pbar
|
|
1908
|
+
|
|
1909
|
+
for n in iterator:
|
|
1910
|
+
time_value = n * solver.dt
|
|
1911
|
+
run_field_update(time_value)
|
|
1912
|
+
|
|
1913
|
+
if solver.tfsf_enabled:
|
|
1914
|
+
apply_tfsf_e_correction(solver, time_value)
|
|
1915
|
+
advance_tfsf_auxiliary_electric(solver)
|
|
1916
|
+
if solver._electric_source_terms:
|
|
1917
|
+
# The electric equivalent current enters the E update through the
|
|
1918
|
+
# incident H field, which lives at the Yee half step. Sample it at
|
|
1919
|
+
# the same n + 1/2 instant as the TFSF electric correction.
|
|
1920
|
+
inject_electric_surface_source_terms(
|
|
1921
|
+
solver,
|
|
1922
|
+
time_value=time_value + 0.5 * float(solver.dt),
|
|
1923
|
+
)
|
|
1924
|
+
|
|
1925
|
+
if solver._source_terms:
|
|
1926
|
+
solver.add_source(time_value=time_value)
|
|
1927
|
+
if run_tail is not None:
|
|
1928
|
+
run_tail()
|
|
1929
|
+
else:
|
|
1930
|
+
solver._apply_dispersive_corrections()
|
|
1931
|
+
if not solver.tfsf_enabled:
|
|
1932
|
+
enforce_pec_boundaries(solver)
|
|
1933
|
+
apply_mur_boundaries(solver)
|
|
1934
|
+
if use_gpu_dft:
|
|
1935
|
+
solver.accumulate_dft_gpu()
|
|
1936
|
+
else:
|
|
1937
|
+
solver.accumulate_dft(n)
|
|
1938
|
+
solver.accumulate_observers(n)
|
|
1939
|
+
solver.accumulate_time_observers(n)
|
|
1940
|
+
|
|
1941
|
+
if shutoff > 0 and (n + 1) % shutoff_check_interval == 0:
|
|
1942
|
+
e_energy = _electric_field_energy(solver)
|
|
1943
|
+
solver._shutoff_peak = torch.maximum(solver._shutoff_peak, e_energy)
|
|
1944
|
+
should_stop = (
|
|
1945
|
+
n >= shutoff_min_step
|
|
1946
|
+
and bool(((solver._shutoff_peak > 0.0) & (e_energy < shutoff * solver._shutoff_peak)).item())
|
|
1947
|
+
)
|
|
1948
|
+
if should_stop:
|
|
1949
|
+
solver._shutoff_triggered = True
|
|
1950
|
+
solver._shutoff_step = n
|
|
1951
|
+
break
|
|
1952
|
+
|
|
1953
|
+
if pbar is not None:
|
|
1954
|
+
should_update_progress = ((n + 1) % solver.progress_update_interval == 0 or n == time_steps - 1)
|
|
1955
|
+
if should_update_progress and solver.dft_enabled and solver.dft_start_step is not None:
|
|
1956
|
+
if n < solver.dft_start_step:
|
|
1957
|
+
pbar.set_postfix({"status": "transient"})
|
|
1958
|
+
else:
|
|
1959
|
+
pbar.set_postfix({"status": "DFT accumulation", "samples": solver.dft_sample_count})
|
|
1960
|
+
|
|
1961
|
+
if enable_plot and n % solver.plot_interval == 0:
|
|
1962
|
+
visualize_slice(solver, n)
|
|
1963
|
+
|
|
1964
|
+
solver._synchronize_device()
|
|
1965
|
+
solver.last_solve_elapsed_s = time.perf_counter() - solve_start
|
|
1966
|
+
if solver._shutoff_triggered:
|
|
1967
|
+
_complete_spectral_normalization(solver, time_steps)
|
|
1968
|
+
if solver.dft_enabled:
|
|
1969
|
+
solver._sync_dft_legacy_state()
|
|
1970
|
+
if solver.observers_enabled:
|
|
1971
|
+
solver._sync_observer_legacy_state()
|
|
1972
|
+
if solver.verbose:
|
|
1973
|
+
print(
|
|
1974
|
+
f"FDTD solve elapsed: {solver.last_solve_elapsed_s:.3f}s "
|
|
1975
|
+
f"({solver.last_solve_elapsed_s * 1e3 / max(time_steps, 1):.3f} ms/step)"
|
|
1976
|
+
)
|
|
1977
|
+
|
|
1978
|
+
output = {}
|
|
1979
|
+
if solver.dft_enabled:
|
|
1980
|
+
output.update(solver.get_frequency_solution(all_frequencies=True))
|
|
1981
|
+
monitors = solver.get_observer_results() if solver.observers_enabled else {}
|
|
1982
|
+
if getattr(solver, "time_observers_enabled", False):
|
|
1983
|
+
monitors = dict(monitors)
|
|
1984
|
+
monitors.update(solver.get_time_observer_results())
|
|
1985
|
+
if monitors:
|
|
1986
|
+
output["observers"] = monitors
|
|
1987
|
+
return output or None
|