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,527 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
|
|
6
|
+
from .common import BOUNDARY_PML
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
DEFAULT_CPML_CONFIG = {
|
|
10
|
+
# A cubic, impedance-matched conductivity ramp is the robust default for
|
|
11
|
+
# the relatively thin (8-12 cell) layers used by Scene boundaries. Large
|
|
12
|
+
# kappa stretching at this thickness creates a resolvable entrance mismatch
|
|
13
|
+
# for propagating waves; complex-frequency shifting remains available through
|
|
14
|
+
# cpml_config and StablePML for low-frequency/late-time workloads.
|
|
15
|
+
"grading_order": 3.0,
|
|
16
|
+
"kappa_max": 1.0,
|
|
17
|
+
"alpha_max": 0.0,
|
|
18
|
+
"reflection": 1e-6,
|
|
19
|
+
"memory_mode": "auto",
|
|
20
|
+
"dense_memory_limit_mib": 64.0,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# StablePML overrides: a higher grading order and a larger complex-frequency
|
|
24
|
+
# shift (alpha_max) trade a little absorption for markedly improved late-time
|
|
25
|
+
# stability, which suppresses the low-frequency growth that stresses a plain CPML.
|
|
26
|
+
STABLE_PML_CONFIG_OVERRIDES = {
|
|
27
|
+
"grading_order": 5.0,
|
|
28
|
+
"kappa_max": 5.0,
|
|
29
|
+
"alpha_max": 0.24,
|
|
30
|
+
"reflection": 1e-9,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Adiabatic absorber grading: gentle cubic conductivity ramp, no kappa/alpha
|
|
34
|
+
# stretching and no auxiliary psi memory.
|
|
35
|
+
ABSORBER_GRADING_ORDER = 3.0
|
|
36
|
+
ABSORBER_REFLECTION = 1e-6
|
|
37
|
+
|
|
38
|
+
_AUTO_CPML_DENSE_FREE_FRACTION = 0.125
|
|
39
|
+
|
|
40
|
+
_CPML_VECTOR_ATTRS = (
|
|
41
|
+
"cpml_kappa_e_x",
|
|
42
|
+
"cpml_kappa_e_y",
|
|
43
|
+
"cpml_kappa_e_z",
|
|
44
|
+
"cpml_inv_kappa_e_x",
|
|
45
|
+
"cpml_inv_kappa_e_y",
|
|
46
|
+
"cpml_inv_kappa_e_z",
|
|
47
|
+
"cpml_b_e_x",
|
|
48
|
+
"cpml_b_e_y",
|
|
49
|
+
"cpml_b_e_z",
|
|
50
|
+
"cpml_c_e_x",
|
|
51
|
+
"cpml_c_e_y",
|
|
52
|
+
"cpml_c_e_z",
|
|
53
|
+
"cpml_kappa_h_x",
|
|
54
|
+
"cpml_kappa_h_y",
|
|
55
|
+
"cpml_kappa_h_z",
|
|
56
|
+
"cpml_inv_kappa_h_x",
|
|
57
|
+
"cpml_inv_kappa_h_y",
|
|
58
|
+
"cpml_inv_kappa_h_z",
|
|
59
|
+
"cpml_b_h_x",
|
|
60
|
+
"cpml_b_h_y",
|
|
61
|
+
"cpml_b_h_z",
|
|
62
|
+
"cpml_c_h_x",
|
|
63
|
+
"cpml_c_h_y",
|
|
64
|
+
"cpml_c_h_z",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
_CPML_MEMORY_ATTRS = (
|
|
68
|
+
"psi_ex_y",
|
|
69
|
+
"psi_ex_z",
|
|
70
|
+
"psi_ey_x",
|
|
71
|
+
"psi_ey_z",
|
|
72
|
+
"psi_ez_x",
|
|
73
|
+
"psi_ez_y",
|
|
74
|
+
"psi_hx_y",
|
|
75
|
+
"psi_hx_z",
|
|
76
|
+
"psi_hy_x",
|
|
77
|
+
"psi_hy_z",
|
|
78
|
+
"psi_hz_x",
|
|
79
|
+
"psi_hz_y",
|
|
80
|
+
)
|
|
81
|
+
_CPML_IMAG_MEMORY_ATTRS = tuple(f"{attr_name}_imag" for attr_name in _CPML_MEMORY_ATTRS)
|
|
82
|
+
|
|
83
|
+
_CPML_MEMORY_SPECS = {
|
|
84
|
+
"psi_ex_y": ("Ex", 1),
|
|
85
|
+
"psi_ex_z": ("Ex", 2),
|
|
86
|
+
"psi_ey_x": ("Ey", 0),
|
|
87
|
+
"psi_ey_z": ("Ey", 2),
|
|
88
|
+
"psi_ez_x": ("Ez", 0),
|
|
89
|
+
"psi_ez_y": ("Ez", 1),
|
|
90
|
+
"psi_hx_y": ("Hx", 1),
|
|
91
|
+
"psi_hx_z": ("Hx", 2),
|
|
92
|
+
"psi_hy_x": ("Hy", 0),
|
|
93
|
+
"psi_hy_z": ("Hy", 2),
|
|
94
|
+
"psi_hz_x": ("Hz", 0),
|
|
95
|
+
"psi_hz_y": ("Hz", 1),
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
_SIGMA_ATTRS = (
|
|
99
|
+
"sigma_x",
|
|
100
|
+
"sigma_y",
|
|
101
|
+
"sigma_z",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _legacy_strength_scale(strength):
|
|
106
|
+
value = float(strength)
|
|
107
|
+
if value <= 0:
|
|
108
|
+
return 1.0
|
|
109
|
+
if value > 100.0:
|
|
110
|
+
return 1.0
|
|
111
|
+
return value
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _cpml_profile_1d(
|
|
115
|
+
positions64,
|
|
116
|
+
nodes64,
|
|
117
|
+
thickness,
|
|
118
|
+
dt,
|
|
119
|
+
vacuum_constant,
|
|
120
|
+
sigma_scale,
|
|
121
|
+
impedance,
|
|
122
|
+
config,
|
|
123
|
+
*,
|
|
124
|
+
high_positions64=None,
|
|
125
|
+
apply_low=True,
|
|
126
|
+
apply_high=True,
|
|
127
|
+
):
|
|
128
|
+
"""Build (kappa, b, c) CPML profile vectors graded by physical depth.
|
|
129
|
+
|
|
130
|
+
``positions64`` are the float64 sample positions of the profile (node
|
|
131
|
+
masters for E profiles, half-point masters for H profiles) and ``nodes64``
|
|
132
|
+
the axis node masters; ``thickness`` is the PML thickness in cells. The
|
|
133
|
+
per-side layer depth and peak conductivity derive from the physical layer
|
|
134
|
+
extents, so nonuniform grids grade correctly and uniform grids reproduce
|
|
135
|
+
the established index-based grading. ``high_positions64`` overrides the
|
|
136
|
+
sample positions used for the high-side depth (H profiles pass their
|
|
137
|
+
cell's outer node so the outermost H sample reaches full depth, matching
|
|
138
|
+
the calibrated legacy grading). Grading runs in float64; the returned
|
|
139
|
+
vectors are float32 on the device of ``sigma_scale``.
|
|
140
|
+
"""
|
|
141
|
+
length = int(len(positions64))
|
|
142
|
+
device = sigma_scale.device
|
|
143
|
+
|
|
144
|
+
def _to_device(array):
|
|
145
|
+
return torch.tensor(array, dtype=torch.float32, device=device).contiguous()
|
|
146
|
+
|
|
147
|
+
thickness = int(thickness)
|
|
148
|
+
if thickness <= 0:
|
|
149
|
+
return (
|
|
150
|
+
_to_device(np.ones(length)),
|
|
151
|
+
_to_device(np.zeros(length)),
|
|
152
|
+
_to_device(np.zeros(length)),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def _interface(*, low):
|
|
156
|
+
# Layer interface measured `thickness` cells inward from the boundary.
|
|
157
|
+
# When the layer is thicker than the axis, extrapolate past the far
|
|
158
|
+
# end with the far-edge cell spacing so degenerate all-PML domains
|
|
159
|
+
# keep the established partial-depth grading.
|
|
160
|
+
cell_count = len(nodes64) - 1
|
|
161
|
+
if thickness <= cell_count:
|
|
162
|
+
return float(nodes64[thickness]) if low else float(nodes64[-1 - thickness])
|
|
163
|
+
extra = thickness - cell_count
|
|
164
|
+
if low:
|
|
165
|
+
return float(nodes64[-1]) + extra * float(nodes64[-1] - nodes64[-2])
|
|
166
|
+
return float(nodes64[0]) - extra * float(nodes64[1] - nodes64[0])
|
|
167
|
+
|
|
168
|
+
grading_order = float(config["grading_order"])
|
|
169
|
+
kappa_max = float(config["kappa_max"])
|
|
170
|
+
alpha_max = float(config["alpha_max"])
|
|
171
|
+
reflection = float(config["reflection"])
|
|
172
|
+
strength = float(sigma_scale.item())
|
|
173
|
+
sigma_numerator = -(grading_order + 1.0) * math.log(reflection) / (2.0 * float(impedance))
|
|
174
|
+
|
|
175
|
+
positions = np.asarray(positions64, dtype=np.float64)
|
|
176
|
+
high_positions = positions if high_positions64 is None else np.asarray(high_positions64, dtype=np.float64)
|
|
177
|
+
depth_low = np.zeros(length, dtype=np.float64)
|
|
178
|
+
depth_high = np.zeros(length, dtype=np.float64)
|
|
179
|
+
sigma_max_low = 0.0
|
|
180
|
+
sigma_max_high = 0.0
|
|
181
|
+
if apply_low:
|
|
182
|
+
low_interface = _interface(low=True)
|
|
183
|
+
layer_low = low_interface - float(nodes64[0])
|
|
184
|
+
depth_low = np.clip((low_interface - positions) / layer_low, 0.0, 1.0)
|
|
185
|
+
sigma_max_low = sigma_numerator / layer_low * strength
|
|
186
|
+
if apply_high:
|
|
187
|
+
high_interface = _interface(low=False)
|
|
188
|
+
layer_high = float(nodes64[-1]) - high_interface
|
|
189
|
+
depth_high = np.clip((high_positions - high_interface) / layer_high, 0.0, 1.0)
|
|
190
|
+
sigma_max_high = sigma_numerator / layer_high * strength
|
|
191
|
+
|
|
192
|
+
use_high = depth_high > depth_low
|
|
193
|
+
depth = np.where(use_high, depth_high, depth_low)
|
|
194
|
+
sigma_peak = np.where(use_high, sigma_max_high, sigma_max_low)
|
|
195
|
+
|
|
196
|
+
active = depth > 0.0
|
|
197
|
+
graded = np.zeros(length, dtype=np.float64)
|
|
198
|
+
graded[active] = depth[active] ** grading_order
|
|
199
|
+
sigma = sigma_peak * graded
|
|
200
|
+
kappa = np.ones(length, dtype=np.float64)
|
|
201
|
+
kappa[active] = 1.0 + (kappa_max - 1.0) * graded[active]
|
|
202
|
+
alpha = np.zeros(length, dtype=np.float64)
|
|
203
|
+
alpha[active] = alpha_max * (1.0 - depth[active])
|
|
204
|
+
|
|
205
|
+
decay = np.exp(-(sigma / kappa + alpha) * float(dt) / float(vacuum_constant))
|
|
206
|
+
denom = sigma + alpha * kappa
|
|
207
|
+
c = np.zeros(length, dtype=np.float64)
|
|
208
|
+
mask = denom > 1e-12
|
|
209
|
+
c[mask] = sigma[mask] * (decay[mask] - 1.0) / (denom[mask] * kappa[mask])
|
|
210
|
+
return _to_device(kappa), _to_device(decay), _to_device(c)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _axis_face_uses_pml(solver, axis: str, *, low: bool) -> bool:
|
|
214
|
+
attr_name = f"boundary_{axis}_{'low' if low else 'high'}_code"
|
|
215
|
+
return int(getattr(solver, attr_name, solver.boundary_code)) == BOUNDARY_PML
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _build_cpml_memory_layout(solver, attr_name):
|
|
219
|
+
field_name, axis = _CPML_MEMORY_SPECS[attr_name]
|
|
220
|
+
field = getattr(solver, field_name)
|
|
221
|
+
axis_name = "xyz"[axis]
|
|
222
|
+
field_shape = tuple(int(size) for size in field.shape)
|
|
223
|
+
axis_size = int(field_shape[axis])
|
|
224
|
+
thickness = min(int(solver.scene.pml_thickness), axis_size)
|
|
225
|
+
low_length = thickness if _axis_face_uses_pml(solver, axis_name, low=True) else 0
|
|
226
|
+
high_capacity = max(axis_size - low_length, 0)
|
|
227
|
+
high_length = min(thickness if _axis_face_uses_pml(solver, axis_name, low=False) else 0, high_capacity)
|
|
228
|
+
compressed_shape = list(field_shape)
|
|
229
|
+
compressed_shape[axis] = low_length + high_length
|
|
230
|
+
layout = {
|
|
231
|
+
"field_name": field_name,
|
|
232
|
+
"field_shape": field_shape,
|
|
233
|
+
"compressed_shape": tuple(compressed_shape),
|
|
234
|
+
"axis": axis,
|
|
235
|
+
"regions": [],
|
|
236
|
+
}
|
|
237
|
+
local_start = 0
|
|
238
|
+
if low_length > 0:
|
|
239
|
+
layout["regions"].append(
|
|
240
|
+
{
|
|
241
|
+
"side": "low",
|
|
242
|
+
"global_start": 0,
|
|
243
|
+
"length": low_length,
|
|
244
|
+
"local_start": local_start,
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
local_start += low_length
|
|
248
|
+
if high_length > 0:
|
|
249
|
+
layout["regions"].append(
|
|
250
|
+
{
|
|
251
|
+
"side": "high",
|
|
252
|
+
"global_start": axis_size - high_length,
|
|
253
|
+
"length": high_length,
|
|
254
|
+
"local_start": local_start,
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
return layout
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _estimate_cpml_memory_bytes(layouts, solver):
|
|
261
|
+
dense_bytes = 0
|
|
262
|
+
slab_bytes = 0
|
|
263
|
+
for layout in layouts.values():
|
|
264
|
+
field = getattr(solver, layout["field_name"])
|
|
265
|
+
element_bytes = int(field.element_size())
|
|
266
|
+
dense_bytes += math.prod(layout["field_shape"]) * element_bytes
|
|
267
|
+
slab_bytes += math.prod(layout["compressed_shape"]) * element_bytes
|
|
268
|
+
return dense_bytes, slab_bytes
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _requested_cpml_memory_mode(solver):
|
|
272
|
+
requested = str(solver.cpml_config.get("memory_mode", "auto")).strip().lower()
|
|
273
|
+
if requested not in {"auto", "dense", "slab"}:
|
|
274
|
+
raise ValueError(
|
|
275
|
+
"cpml_config['memory_mode'] must be one of 'auto', 'dense', or 'slab'."
|
|
276
|
+
)
|
|
277
|
+
return requested
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _resolve_cpml_memory_mode(solver, dense_bytes):
|
|
281
|
+
requested = _requested_cpml_memory_mode(solver)
|
|
282
|
+
solver._cpml_memory_mode_requested = requested
|
|
283
|
+
if requested != "auto":
|
|
284
|
+
solver._cpml_dense_memory_limit_bytes = None
|
|
285
|
+
solver._cpml_auto_free_bytes = None
|
|
286
|
+
return requested
|
|
287
|
+
|
|
288
|
+
limit_mib = float(
|
|
289
|
+
solver.cpml_config.get("dense_memory_limit_mib", DEFAULT_CPML_CONFIG["dense_memory_limit_mib"])
|
|
290
|
+
)
|
|
291
|
+
limit_bytes = max(int(limit_mib * 1024 * 1024), 0)
|
|
292
|
+
free_bytes = None
|
|
293
|
+
try:
|
|
294
|
+
free_bytes, _ = torch.cuda.mem_get_info(solver.device)
|
|
295
|
+
except (RuntimeError, ValueError):
|
|
296
|
+
free_bytes = None
|
|
297
|
+
if free_bytes is not None:
|
|
298
|
+
limit_bytes = min(limit_bytes, int(float(free_bytes) * _AUTO_CPML_DENSE_FREE_FRACTION))
|
|
299
|
+
solver._cpml_dense_memory_limit_bytes = limit_bytes
|
|
300
|
+
solver._cpml_auto_free_bytes = None if free_bytes is None else int(free_bytes)
|
|
301
|
+
return "dense" if dense_bytes <= limit_bytes else "slab"
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _allocate_cpml_memory_variables(solver):
|
|
305
|
+
layouts = {attr_name: _build_cpml_memory_layout(solver, attr_name) for attr_name in _CPML_MEMORY_ATTRS}
|
|
306
|
+
dense_bytes, slab_bytes = _estimate_cpml_memory_bytes(layouts, solver)
|
|
307
|
+
memory_multiplier = 2 if bool(getattr(solver, "complex_fields_enabled", False)) else 1
|
|
308
|
+
dense_bytes *= memory_multiplier
|
|
309
|
+
slab_bytes *= memory_multiplier
|
|
310
|
+
memory_mode = _resolve_cpml_memory_mode(solver, dense_bytes)
|
|
311
|
+
|
|
312
|
+
solver._cpml_memory_layouts = {}
|
|
313
|
+
solver._cpml_memory_mode = memory_mode
|
|
314
|
+
solver._cpml_dense_memory_bytes = dense_bytes
|
|
315
|
+
solver._cpml_slab_memory_bytes = slab_bytes
|
|
316
|
+
solver._cpml_allocated_memory_bytes = 0
|
|
317
|
+
|
|
318
|
+
for attr_name, layout in layouts.items():
|
|
319
|
+
field = getattr(solver, layout["field_name"])
|
|
320
|
+
target_shape = layout["field_shape"] if memory_mode == "dense" else layout["compressed_shape"]
|
|
321
|
+
tensor = torch.zeros(target_shape, device=solver.device, dtype=field.dtype)
|
|
322
|
+
if memory_mode == "slab":
|
|
323
|
+
solver._cpml_memory_layouts[attr_name] = layout
|
|
324
|
+
solver._cpml_allocated_memory_bytes += tensor.numel() * tensor.element_size()
|
|
325
|
+
setattr(solver, attr_name, tensor)
|
|
326
|
+
if bool(getattr(solver, "complex_fields_enabled", False)):
|
|
327
|
+
imag_attr_name = f"{attr_name}_imag"
|
|
328
|
+
imag_tensor = torch.zeros(target_shape, device=solver.device, dtype=field.dtype)
|
|
329
|
+
if memory_mode == "slab":
|
|
330
|
+
solver._cpml_memory_layouts[imag_attr_name] = layout
|
|
331
|
+
solver._cpml_allocated_memory_bytes += imag_tensor.numel() * imag_tensor.element_size()
|
|
332
|
+
setattr(solver, imag_attr_name, imag_tensor)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def expand_cpml_memory_tensor(solver, attr_name):
|
|
336
|
+
tensor = getattr(solver, attr_name)
|
|
337
|
+
layout = getattr(solver, "_cpml_memory_layouts", {}).get(attr_name)
|
|
338
|
+
if layout is None:
|
|
339
|
+
return tensor
|
|
340
|
+
|
|
341
|
+
dense = torch.zeros(layout["field_shape"], device=tensor.device, dtype=tensor.dtype)
|
|
342
|
+
axis = int(layout["axis"])
|
|
343
|
+
for region in layout["regions"]:
|
|
344
|
+
length = int(region["length"])
|
|
345
|
+
if length <= 0:
|
|
346
|
+
continue
|
|
347
|
+
dense.narrow(axis, int(region["global_start"]), length).copy_(
|
|
348
|
+
tensor.narrow(axis, int(region["local_start"]), length)
|
|
349
|
+
)
|
|
350
|
+
return dense
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _initialize_neutral_cpml_buffers(solver):
|
|
354
|
+
solver.cpml_kappa_e_x = torch.ones(solver.Nx, device=solver.device, dtype=torch.float32)
|
|
355
|
+
solver.cpml_kappa_e_y = torch.ones(solver.Ny, device=solver.device, dtype=torch.float32)
|
|
356
|
+
solver.cpml_kappa_e_z = torch.ones(solver.Nz, device=solver.device, dtype=torch.float32)
|
|
357
|
+
solver.cpml_b_e_x = torch.zeros(solver.Nx, device=solver.device, dtype=torch.float32)
|
|
358
|
+
solver.cpml_b_e_y = torch.zeros(solver.Ny, device=solver.device, dtype=torch.float32)
|
|
359
|
+
solver.cpml_b_e_z = torch.zeros(solver.Nz, device=solver.device, dtype=torch.float32)
|
|
360
|
+
solver.cpml_c_e_x = torch.zeros(solver.Nx, device=solver.device, dtype=torch.float32)
|
|
361
|
+
solver.cpml_c_e_y = torch.zeros(solver.Ny, device=solver.device, dtype=torch.float32)
|
|
362
|
+
solver.cpml_c_e_z = torch.zeros(solver.Nz, device=solver.device, dtype=torch.float32)
|
|
363
|
+
|
|
364
|
+
solver.cpml_kappa_h_x = torch.ones(solver.Nx - 1, device=solver.device, dtype=torch.float32)
|
|
365
|
+
solver.cpml_kappa_h_y = torch.ones(solver.Ny - 1, device=solver.device, dtype=torch.float32)
|
|
366
|
+
solver.cpml_kappa_h_z = torch.ones(solver.Nz - 1, device=solver.device, dtype=torch.float32)
|
|
367
|
+
solver.cpml_b_h_x = torch.zeros(solver.Nx - 1, device=solver.device, dtype=torch.float32)
|
|
368
|
+
solver.cpml_b_h_y = torch.zeros(solver.Ny - 1, device=solver.device, dtype=torch.float32)
|
|
369
|
+
solver.cpml_b_h_z = torch.zeros(solver.Nz - 1, device=solver.device, dtype=torch.float32)
|
|
370
|
+
solver.cpml_c_h_x = torch.zeros(solver.Nx - 1, device=solver.device, dtype=torch.float32)
|
|
371
|
+
solver.cpml_c_h_y = torch.zeros(solver.Ny - 1, device=solver.device, dtype=torch.float32)
|
|
372
|
+
solver.cpml_c_h_z = torch.zeros(solver.Nz - 1, device=solver.device, dtype=torch.float32)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _allocate_sigma_tensors(solver):
|
|
376
|
+
solver.sigma_x = torch.zeros((solver.Nx, solver.Ny, solver.Nz), device=solver.device, dtype=torch.float32)
|
|
377
|
+
solver.sigma_y = torch.zeros((solver.Nx, solver.Ny, solver.Nz), device=solver.device, dtype=torch.float32)
|
|
378
|
+
solver.sigma_z = torch.zeros((solver.Nx, solver.Ny, solver.Nz), device=solver.device, dtype=torch.float32)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _set_inv_kappa_buffers(solver):
|
|
382
|
+
solver.cpml_inv_kappa_e_x = torch.reciprocal(solver.cpml_kappa_e_x).contiguous()
|
|
383
|
+
solver.cpml_inv_kappa_e_y = torch.reciprocal(solver.cpml_kappa_e_y).contiguous()
|
|
384
|
+
solver.cpml_inv_kappa_e_z = torch.reciprocal(solver.cpml_kappa_e_z).contiguous()
|
|
385
|
+
solver.cpml_inv_kappa_h_x = torch.reciprocal(solver.cpml_kappa_h_x).contiguous()
|
|
386
|
+
solver.cpml_inv_kappa_h_y = torch.reciprocal(solver.cpml_kappa_h_y).contiguous()
|
|
387
|
+
solver.cpml_inv_kappa_h_z = torch.reciprocal(solver.cpml_kappa_h_z).contiguous()
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _clear_auxiliary_state(solver):
|
|
391
|
+
for attr in _CPML_VECTOR_ATTRS + _CPML_MEMORY_ATTRS + _CPML_IMAG_MEMORY_ATTRS + _SIGMA_ATTRS:
|
|
392
|
+
setattr(solver, attr, None)
|
|
393
|
+
solver._cpml_memory_layouts = {}
|
|
394
|
+
solver._cpml_memory_mode = "none"
|
|
395
|
+
solver._cpml_dense_memory_bytes = 0
|
|
396
|
+
solver._cpml_slab_memory_bytes = 0
|
|
397
|
+
solver._cpml_allocated_memory_bytes = 0
|
|
398
|
+
solver._cpml_dense_memory_limit_bytes = None
|
|
399
|
+
solver._cpml_auto_free_bytes = None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def build_cpml_profiles(solver):
|
|
403
|
+
strength_scale = _legacy_strength_scale(solver.scene.pml_strength)
|
|
404
|
+
scale_tensor = torch.tensor(strength_scale, device=solver.device, dtype=torch.float32)
|
|
405
|
+
magnetic_scale = scale_tensor * (solver.mu0 / solver.eps0)
|
|
406
|
+
thickness = solver.scene.pml_thickness
|
|
407
|
+
eta0 = math.sqrt(solver.mu0 / solver.eps0)
|
|
408
|
+
scene = solver.scene
|
|
409
|
+
|
|
410
|
+
axis_masters = {
|
|
411
|
+
"x": (scene.x_nodes64, scene.x_half64),
|
|
412
|
+
"y": (scene.y_nodes64, scene.y_half64),
|
|
413
|
+
"z": (scene.z_nodes64, scene.z_half64),
|
|
414
|
+
}
|
|
415
|
+
for axis, (nodes64, half64) in axis_masters.items():
|
|
416
|
+
apply_low = _axis_face_uses_pml(solver, axis, low=True)
|
|
417
|
+
apply_high = _axis_face_uses_pml(solver, axis, low=False)
|
|
418
|
+
kappa_e, b_e, c_e = _cpml_profile_1d(
|
|
419
|
+
nodes64,
|
|
420
|
+
nodes64,
|
|
421
|
+
thickness,
|
|
422
|
+
solver.dt,
|
|
423
|
+
solver.eps0,
|
|
424
|
+
scale_tensor,
|
|
425
|
+
eta0,
|
|
426
|
+
solver.cpml_config,
|
|
427
|
+
apply_low=apply_low,
|
|
428
|
+
apply_high=apply_high,
|
|
429
|
+
)
|
|
430
|
+
kappa_h, b_h, c_h = _cpml_profile_1d(
|
|
431
|
+
half64,
|
|
432
|
+
nodes64,
|
|
433
|
+
thickness,
|
|
434
|
+
solver.dt,
|
|
435
|
+
solver.mu0,
|
|
436
|
+
magnetic_scale,
|
|
437
|
+
eta0,
|
|
438
|
+
solver.cpml_config,
|
|
439
|
+
high_positions64=nodes64[1:],
|
|
440
|
+
apply_low=apply_low,
|
|
441
|
+
apply_high=apply_high,
|
|
442
|
+
)
|
|
443
|
+
setattr(solver, f"cpml_kappa_e_{axis}", kappa_e)
|
|
444
|
+
setattr(solver, f"cpml_b_e_{axis}", b_e)
|
|
445
|
+
setattr(solver, f"cpml_c_e_{axis}", c_e)
|
|
446
|
+
setattr(solver, f"cpml_kappa_h_{axis}", kappa_h)
|
|
447
|
+
setattr(solver, f"cpml_b_h_{axis}", b_h)
|
|
448
|
+
setattr(solver, f"cpml_c_h_{axis}", c_h)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def initialize_cpml_state(solver):
|
|
452
|
+
_clear_auxiliary_state(solver)
|
|
453
|
+
build_cpml_profiles(solver)
|
|
454
|
+
_set_inv_kappa_buffers(solver)
|
|
455
|
+
_allocate_cpml_memory_variables(solver)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def initialize_stable_pml_state(solver):
|
|
459
|
+
# StablePML reuses the full CPML machinery with a stability-oriented profile.
|
|
460
|
+
config = dict(solver.cpml_config)
|
|
461
|
+
config.update(STABLE_PML_CONFIG_OVERRIDES)
|
|
462
|
+
solver.cpml_config = config
|
|
463
|
+
initialize_cpml_state(solver)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def initialize_neutral_boundary_state(solver):
|
|
467
|
+
_clear_auxiliary_state(solver)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def initialize_simple_pml_state(solver):
|
|
471
|
+
_clear_auxiliary_state(solver)
|
|
472
|
+
|
|
473
|
+
thickness = solver.scene.pml_thickness
|
|
474
|
+
_allocate_sigma_tensors(solver)
|
|
475
|
+
sigma_max = solver.scene.pml_strength * 2.0 / (solver.dt * min(solver.min_dx, solver.min_dy, solver.min_dz))
|
|
476
|
+
for i in range(thickness):
|
|
477
|
+
x = (thickness - i) / thickness
|
|
478
|
+
sigma = sigma_max * x**4
|
|
479
|
+
if _axis_face_uses_pml(solver, "x", low=True):
|
|
480
|
+
solver.sigma_x[i, :, :] = sigma
|
|
481
|
+
if _axis_face_uses_pml(solver, "x", low=False):
|
|
482
|
+
solver.sigma_x[-(i + 1), :, :] = sigma
|
|
483
|
+
if _axis_face_uses_pml(solver, "y", low=True):
|
|
484
|
+
solver.sigma_y[:, i, :] = sigma
|
|
485
|
+
if _axis_face_uses_pml(solver, "y", low=False):
|
|
486
|
+
solver.sigma_y[:, -(i + 1), :] = sigma
|
|
487
|
+
if _axis_face_uses_pml(solver, "z", low=True):
|
|
488
|
+
solver.sigma_z[:, :, i] = sigma
|
|
489
|
+
if _axis_face_uses_pml(solver, "z", low=False):
|
|
490
|
+
solver.sigma_z[:, :, -(i + 1)] = sigma
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def initialize_absorber_state(solver):
|
|
494
|
+
# Adiabatic graded-conductivity absorber. Shares the semi-implicit sigma
|
|
495
|
+
# branch used by the simple PML but with a gentler cubic ramp and a
|
|
496
|
+
# reflection-calibrated peak conductivity, and no kappa/alpha stretching or
|
|
497
|
+
# auxiliary psi memory.
|
|
498
|
+
_clear_auxiliary_state(solver)
|
|
499
|
+
|
|
500
|
+
thickness = solver.scene.pml_thickness
|
|
501
|
+
_allocate_sigma_tensors(solver)
|
|
502
|
+
eta0 = math.sqrt(solver.mu0 / solver.eps0)
|
|
503
|
+
grading_order = ABSORBER_GRADING_ORDER
|
|
504
|
+
min_delta = min(solver.min_dx, solver.min_dy, solver.min_dz)
|
|
505
|
+
# Physical peak conductivity (S/m) calibrated to the target round-trip
|
|
506
|
+
# reflection, then converted to the 1/s scale that the semi-implicit sigma
|
|
507
|
+
# update expects (the stored sigma multiplies dt directly, i.e. sigma/eps0).
|
|
508
|
+
sigma_phys_max = -(grading_order + 1.0) * math.log(ABSORBER_REFLECTION) / (
|
|
509
|
+
2.0 * eta0 * thickness * min_delta
|
|
510
|
+
)
|
|
511
|
+
sigma_max = sigma_phys_max / solver.eps0
|
|
512
|
+
sigma_max *= _legacy_strength_scale(solver.scene.pml_strength)
|
|
513
|
+
for i in range(thickness):
|
|
514
|
+
x = (thickness - i) / thickness
|
|
515
|
+
sigma = sigma_max * x**grading_order
|
|
516
|
+
if _axis_face_uses_pml(solver, "x", low=True):
|
|
517
|
+
solver.sigma_x[i, :, :] = sigma
|
|
518
|
+
if _axis_face_uses_pml(solver, "x", low=False):
|
|
519
|
+
solver.sigma_x[-(i + 1), :, :] = sigma
|
|
520
|
+
if _axis_face_uses_pml(solver, "y", low=True):
|
|
521
|
+
solver.sigma_y[:, i, :] = sigma
|
|
522
|
+
if _axis_face_uses_pml(solver, "y", low=False):
|
|
523
|
+
solver.sigma_y[:, -(i + 1), :] = sigma
|
|
524
|
+
if _axis_face_uses_pml(solver, "z", low=True):
|
|
525
|
+
solver.sigma_z[:, :, i] = sigma
|
|
526
|
+
if _axis_face_uses_pml(solver, "z", low=False):
|
|
527
|
+
solver.sigma_z[:, :, -(i + 1)] = sigma
|