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,209 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
|
|
5
|
+
from .common import BOUNDARY_KIND_TO_CODE, BOUNDARY_PEC, initialize_complex_fields
|
|
6
|
+
from .bloch import resolve_bloch_phase_factors, resolve_bloch_wavevector
|
|
7
|
+
from .cpml import (
|
|
8
|
+
initialize_absorber_state,
|
|
9
|
+
initialize_cpml_state,
|
|
10
|
+
initialize_neutral_boundary_state,
|
|
11
|
+
initialize_simple_pml_state,
|
|
12
|
+
initialize_stable_pml_state,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _configure_face_boundary_codes(solver):
|
|
17
|
+
symmetry = getattr(solver.scene, "symmetry", (None, None, None))
|
|
18
|
+
boundary = solver.scene.boundary
|
|
19
|
+
face_codes = {
|
|
20
|
+
axis: [
|
|
21
|
+
BOUNDARY_KIND_TO_CODE[boundary.face_kind(axis, "low")],
|
|
22
|
+
BOUNDARY_KIND_TO_CODE[boundary.face_kind(axis, "high")],
|
|
23
|
+
]
|
|
24
|
+
for axis in ("x", "y", "z")
|
|
25
|
+
}
|
|
26
|
+
for axis, entry in zip(("x", "y", "z"), symmetry):
|
|
27
|
+
if entry is None:
|
|
28
|
+
continue
|
|
29
|
+
mode, face = entry
|
|
30
|
+
if boundary.face_kind(axis, face) not in {"none", "pml"}:
|
|
31
|
+
raise ValueError(
|
|
32
|
+
f"Scene.symmetry on the {axis}-{face} face requires "
|
|
33
|
+
"BoundarySpec.none() or BoundarySpec.pml(...)."
|
|
34
|
+
)
|
|
35
|
+
face_index = 0 if face == "low" else 1
|
|
36
|
+
face_codes[axis][face_index] = BOUNDARY_KIND_TO_CODE[mode.lower()]
|
|
37
|
+
|
|
38
|
+
_validate_symmetry_source_placement(solver.scene, symmetry)
|
|
39
|
+
|
|
40
|
+
for axis in ("x", "y", "z"):
|
|
41
|
+
low_code, high_code = (int(face_codes[axis][0]), int(face_codes[axis][1]))
|
|
42
|
+
setattr(solver, f"boundary_{axis}_low_code", low_code)
|
|
43
|
+
setattr(solver, f"boundary_{axis}_high_code", high_code)
|
|
44
|
+
|
|
45
|
+
solver.has_pec_faces = any(
|
|
46
|
+
code == BOUNDARY_PEC
|
|
47
|
+
for codes in face_codes.values()
|
|
48
|
+
for code in codes
|
|
49
|
+
)
|
|
50
|
+
solver.has_pml_faces = any(
|
|
51
|
+
code == BOUNDARY_KIND_TO_CODE["pml"]
|
|
52
|
+
for codes in face_codes.values()
|
|
53
|
+
for code in codes
|
|
54
|
+
)
|
|
55
|
+
solver.has_periodic_axes = tuple(
|
|
56
|
+
axis
|
|
57
|
+
for axis in ("x", "y", "z")
|
|
58
|
+
if boundary.axis_kind(axis) == "periodic"
|
|
59
|
+
)
|
|
60
|
+
solver.has_bloch_axes = tuple(
|
|
61
|
+
axis
|
|
62
|
+
for axis in ("x", "y", "z")
|
|
63
|
+
if boundary.axis_kind(axis) == "bloch"
|
|
64
|
+
)
|
|
65
|
+
solver.mur_faces = tuple(
|
|
66
|
+
(axis, side)
|
|
67
|
+
for axis in ("x", "y", "z")
|
|
68
|
+
for side in ("low", "high")
|
|
69
|
+
if boundary.face_kind(axis, side) == "mur"
|
|
70
|
+
)
|
|
71
|
+
solver.has_mur_faces = bool(solver.mur_faces)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
_AXIS_INDEX = {"x": 0, "y": 1, "z": 2}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _source_center(source):
|
|
78
|
+
"""Return a point-like source center coordinate tuple, or ``None`` if the
|
|
79
|
+
source spans the domain (e.g. plane waves) and has no single position."""
|
|
80
|
+
for attr in ("position", "center"):
|
|
81
|
+
value = getattr(source, attr, None)
|
|
82
|
+
if value is not None:
|
|
83
|
+
return tuple(float(v) for v in value)
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _validate_symmetry_source_placement(scene, symmetry):
|
|
88
|
+
"""Reject point-like sources located in the folded-away half of the domain.
|
|
89
|
+
|
|
90
|
+
A source may sit on the symmetry plane or anywhere in the kept half; a source
|
|
91
|
+
beyond the plane lives in the removed half and cannot be represented on the
|
|
92
|
+
folded grid, so we raise clearly instead of silently dropping it.
|
|
93
|
+
"""
|
|
94
|
+
if all(entry is None for entry in symmetry):
|
|
95
|
+
return
|
|
96
|
+
resolver = getattr(scene, "resolved_sources", None)
|
|
97
|
+
sources = resolver() if callable(resolver) else getattr(scene, "sources", ())
|
|
98
|
+
bounds = scene.domain.bounds
|
|
99
|
+
tol = 1e-9
|
|
100
|
+
for source in sources:
|
|
101
|
+
center = _source_center(source)
|
|
102
|
+
if center is None:
|
|
103
|
+
continue
|
|
104
|
+
for axis, entry in zip(("x", "y", "z"), symmetry):
|
|
105
|
+
if entry is None:
|
|
106
|
+
continue
|
|
107
|
+
_mode, face = entry
|
|
108
|
+
axis_idx = _AXIS_INDEX[axis]
|
|
109
|
+
low, high = float(bounds[axis_idx][0]), float(bounds[axis_idx][1])
|
|
110
|
+
coord = center[axis_idx]
|
|
111
|
+
span = max(high - low, tol)
|
|
112
|
+
plane = low if face == "low" else high
|
|
113
|
+
if face == "low" and coord < plane - tol * span:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Source at {axis}={coord:g} lies in the folded-away half below the "
|
|
116
|
+
f"{axis}-low symmetry plane at {plane:g}; place it on the plane or in "
|
|
117
|
+
"the kept half of the domain."
|
|
118
|
+
)
|
|
119
|
+
if face == "high" and coord > plane + tol * span:
|
|
120
|
+
raise ValueError(
|
|
121
|
+
f"Source at {axis}={coord:g} lies in the folded-away half above the "
|
|
122
|
+
f"{axis}-high symmetry plane at {plane:g}; place it on the plane or in "
|
|
123
|
+
"the kept half of the domain."
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def initialize_boundary_state(solver):
|
|
128
|
+
solver.boundary_kind = solver.scene.boundary.kind
|
|
129
|
+
solver.boundary_code = (
|
|
130
|
+
BOUNDARY_KIND_TO_CODE[solver.boundary_kind]
|
|
131
|
+
if solver.boundary_kind in BOUNDARY_KIND_TO_CODE
|
|
132
|
+
else None
|
|
133
|
+
)
|
|
134
|
+
_configure_face_boundary_codes(solver)
|
|
135
|
+
|
|
136
|
+
solver.complex_fields_enabled = solver.scene.boundary.uses_kind("bloch")
|
|
137
|
+
if solver.complex_fields_enabled:
|
|
138
|
+
initialize_complex_fields(solver)
|
|
139
|
+
|
|
140
|
+
solver.resolved_bloch_wavevector = resolve_bloch_wavevector(solver)
|
|
141
|
+
phases = resolve_bloch_phase_factors(solver)
|
|
142
|
+
solver.boundary_phase_cos = tuple(float(phase.real) for phase in phases)
|
|
143
|
+
solver.boundary_phase_sin = tuple(float(phase.imag) for phase in phases)
|
|
144
|
+
solver.active_absorber_type = solver.absorber_type if solver.scene.boundary.uses_kind("pml") else "none"
|
|
145
|
+
solver.uses_cpml = solver.active_absorber_type in ("cpml", "stablepml")
|
|
146
|
+
|
|
147
|
+
if solver.scene.boundary.uses_kind("pml"):
|
|
148
|
+
if solver.absorber_type == "cpml":
|
|
149
|
+
initialize_cpml_state(solver)
|
|
150
|
+
elif solver.absorber_type == "stablepml":
|
|
151
|
+
initialize_stable_pml_state(solver)
|
|
152
|
+
elif solver.absorber_type == "pml":
|
|
153
|
+
initialize_simple_pml_state(solver)
|
|
154
|
+
elif solver.absorber_type == "absorber":
|
|
155
|
+
initialize_absorber_state(solver)
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
"PML boundaries require absorber_type to be 'cpml', 'stablepml', 'pml', or 'absorber'."
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
initialize_neutral_boundary_state(solver)
|
|
162
|
+
|
|
163
|
+
initialize_mur_state(solver)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Tangential electric-field components on each outer face and the Yee grid axis
|
|
167
|
+
# they are indexed along for the normal direction of that face.
|
|
168
|
+
_MUR_FACE_COMPONENTS = {
|
|
169
|
+
"x": (("Ey", 0), ("Ez", 0)),
|
|
170
|
+
"y": (("Ex", 1), ("Ez", 1)),
|
|
171
|
+
"z": (("Ex", 2), ("Ey", 2)),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def initialize_mur_state(solver):
|
|
176
|
+
solver._mur_state = []
|
|
177
|
+
if not getattr(solver, "has_mur_faces", False):
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
scene = solver.scene
|
|
181
|
+
axis_primal = {"x": scene.dx_primal64, "y": scene.dy_primal64, "z": scene.dz_primal64}
|
|
182
|
+
speed_dt = solver.c * solver.dt
|
|
183
|
+
for axis, side in solver.mur_faces:
|
|
184
|
+
# Mur advection uses the local boundary-cell spacing of each face. The
|
|
185
|
+
# coefficient depends only on c, dt, and that spacing, so it is constant
|
|
186
|
+
# across time steps and precomputed once here.
|
|
187
|
+
primal = axis_primal[axis]
|
|
188
|
+
delta = float(primal[0] if side == "low" else primal[-1])
|
|
189
|
+
coef = (speed_dt - delta) / (speed_dt + delta)
|
|
190
|
+
for field_name, field_axis in _MUR_FACE_COMPONENTS[axis]:
|
|
191
|
+
field = getattr(solver, field_name)
|
|
192
|
+
size = int(field.shape[field_axis])
|
|
193
|
+
boundary_index = 0 if side == "low" else size - 1
|
|
194
|
+
adjacent_index = 1 if side == "low" else size - 2
|
|
195
|
+
# Persistent boundary / first-interior plane buffers, updated in place
|
|
196
|
+
# by the Mur kernel each step (no per-step allocation). They start at
|
|
197
|
+
# zero, matching the initial field state.
|
|
198
|
+
plane_shape = tuple(field.shape[:field_axis] + field.shape[field_axis + 1:])
|
|
199
|
+
solver._mur_state.append(
|
|
200
|
+
{
|
|
201
|
+
"field": field_name,
|
|
202
|
+
"axis": int(field_axis),
|
|
203
|
+
"boundary_index": int(boundary_index),
|
|
204
|
+
"adjacent_index": int(adjacent_index),
|
|
205
|
+
"coef": float(coef),
|
|
206
|
+
"prev_boundary": torch.zeros(plane_shape, device=solver.device, dtype=field.dtype),
|
|
207
|
+
"prev_adjacent": torch.zeros(plane_shape, device=solver.device, dtype=field.dtype),
|
|
208
|
+
}
|
|
209
|
+
)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
from .boundary import expand_cpml_memory_tensor
|
|
8
|
+
|
|
9
|
+
_FIELD_STATE_NAMES = ("Ex", "Ey", "Ez", "Hx", "Hy", "Hz")
|
|
10
|
+
_COMPLEX_FIELD_STATE_NAMES = ("Ex_imag", "Ey_imag", "Ez_imag", "Hx_imag", "Hy_imag", "Hz_imag")
|
|
11
|
+
_CPML_STATE_NAMES = (
|
|
12
|
+
"psi_ex_y",
|
|
13
|
+
"psi_ex_z",
|
|
14
|
+
"psi_ey_x",
|
|
15
|
+
"psi_ey_z",
|
|
16
|
+
"psi_ez_x",
|
|
17
|
+
"psi_ez_y",
|
|
18
|
+
"psi_hx_y",
|
|
19
|
+
"psi_hx_z",
|
|
20
|
+
"psi_hy_x",
|
|
21
|
+
"psi_hy_z",
|
|
22
|
+
"psi_hz_x",
|
|
23
|
+
"psi_hz_y",
|
|
24
|
+
)
|
|
25
|
+
_CPML_COMPLEX_STATE_NAMES = tuple(f"{name}_imag" for name in _CPML_STATE_NAMES)
|
|
26
|
+
_TFSF_AUXILIARY_STATE_NAMES = (
|
|
27
|
+
"tfsf_aux_electric",
|
|
28
|
+
"tfsf_aux_magnetic",
|
|
29
|
+
)
|
|
30
|
+
_DISPERSIVE_STATE_TENSORS = {
|
|
31
|
+
"debye": ("polarization", "current"),
|
|
32
|
+
"drude": ("current",),
|
|
33
|
+
"lorentz": ("polarization", "current"),
|
|
34
|
+
}
|
|
35
|
+
_CHECKPOINT_SCHEMA_VERSION = 1
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def dispersive_state_name(component_name: str, model_name: str, index: int, tensor_name: str) -> str:
|
|
39
|
+
return f"{model_name}_{component_name}_{int(index)}_{tensor_name}"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def iter_dispersive_state_specs(solver):
|
|
43
|
+
if not getattr(solver, "dispersive_enabled", False):
|
|
44
|
+
return
|
|
45
|
+
templates = getattr(solver, "_dispersive_templates", {})
|
|
46
|
+
for component_name in ("Ex", "Ey", "Ez"):
|
|
47
|
+
component_templates = templates.get(component_name, {})
|
|
48
|
+
for model_name in ("debye", "drude", "lorentz"):
|
|
49
|
+
tensor_names = _DISPERSIVE_STATE_TENSORS[model_name]
|
|
50
|
+
for index, entry in enumerate(component_templates.get(model_name, ())):
|
|
51
|
+
yield component_name, model_name, index, tensor_names, entry
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def iter_magnetic_dispersive_state_specs(solver):
|
|
55
|
+
if not getattr(solver, "magnetic_dispersive_enabled", False):
|
|
56
|
+
return
|
|
57
|
+
templates = getattr(solver, "_magnetic_dispersive_templates", {})
|
|
58
|
+
for component_name in ("Hx", "Hy", "Hz"):
|
|
59
|
+
component_templates = templates.get(component_name, {})
|
|
60
|
+
for model_name in ("debye", "drude", "lorentz"):
|
|
61
|
+
tensor_names = _DISPERSIVE_STATE_TENSORS[model_name]
|
|
62
|
+
for index, entry in enumerate(component_templates.get(model_name, ())):
|
|
63
|
+
yield component_name, model_name, index, tensor_names, entry
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True)
|
|
67
|
+
class FDTDCheckpointSchema:
|
|
68
|
+
version: int
|
|
69
|
+
field_names: tuple[str, ...]
|
|
70
|
+
complex_field_names: tuple[str, ...]
|
|
71
|
+
cpml_state_names: tuple[str, ...]
|
|
72
|
+
tfsf_auxiliary_state_names: tuple[str, ...]
|
|
73
|
+
dispersive_state_names: tuple[str, ...]
|
|
74
|
+
magnetic_dispersive_state_names: tuple[str, ...] = ()
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def state_names(self) -> tuple[str, ...]:
|
|
78
|
+
return (
|
|
79
|
+
self.field_names
|
|
80
|
+
+ self.complex_field_names
|
|
81
|
+
+ self.cpml_state_names
|
|
82
|
+
+ self.tfsf_auxiliary_state_names
|
|
83
|
+
+ self.dispersive_state_names
|
|
84
|
+
+ self.magnetic_dispersive_state_names
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def checkpoint_schema(solver) -> FDTDCheckpointSchema:
|
|
89
|
+
complex_field_names = _COMPLEX_FIELD_STATE_NAMES if bool(getattr(solver, "complex_fields_enabled", False)) else ()
|
|
90
|
+
cpml_state_names = ()
|
|
91
|
+
if getattr(solver, "uses_cpml", False):
|
|
92
|
+
cpml_state_names = _CPML_STATE_NAMES
|
|
93
|
+
if bool(getattr(solver, "complex_fields_enabled", False)):
|
|
94
|
+
cpml_state_names = cpml_state_names + _CPML_COMPLEX_STATE_NAMES
|
|
95
|
+
|
|
96
|
+
tfsf_auxiliary_state_names = ()
|
|
97
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
98
|
+
tfsf_state = getattr(solver, "_tfsf_state", None)
|
|
99
|
+
if tfsf_state is not None and tfsf_state.get("auxiliary_grid") is not None:
|
|
100
|
+
tfsf_auxiliary_state_names = _TFSF_AUXILIARY_STATE_NAMES
|
|
101
|
+
|
|
102
|
+
dispersive_state_names = []
|
|
103
|
+
for component_name, model_name, index, tensor_names, _entry in iter_dispersive_state_specs(solver) or ():
|
|
104
|
+
for tensor_name in tensor_names:
|
|
105
|
+
dispersive_state_names.append(dispersive_state_name(component_name, model_name, index, tensor_name))
|
|
106
|
+
if bool(getattr(solver, "complex_fields_enabled", False)):
|
|
107
|
+
# Bloch (complex-field) runs keep an imaginary ADE replica per electric
|
|
108
|
+
# pole; the reverse replay differentiates through it, so it must be part
|
|
109
|
+
# of the frozen checkpoint layout too.
|
|
110
|
+
for component_name, model_name, index, tensor_names, _entry in iter_dispersive_state_specs(solver) or ():
|
|
111
|
+
for tensor_name in tensor_names:
|
|
112
|
+
dispersive_state_names.append(
|
|
113
|
+
dispersive_state_name(component_name, model_name, index, tensor_name) + "_imag"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
magnetic_dispersive_state_names = []
|
|
117
|
+
for component_name, model_name, index, tensor_names, _entry in iter_magnetic_dispersive_state_specs(solver) or ():
|
|
118
|
+
for tensor_name in tensor_names:
|
|
119
|
+
magnetic_dispersive_state_names.append(
|
|
120
|
+
dispersive_state_name(component_name, model_name, index, tensor_name)
|
|
121
|
+
)
|
|
122
|
+
if bool(getattr(solver, "complex_fields_enabled", False)):
|
|
123
|
+
magnetic_dispersive_state_names.extend(
|
|
124
|
+
name + "_imag" for name in tuple(magnetic_dispersive_state_names)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return FDTDCheckpointSchema(
|
|
128
|
+
version=_CHECKPOINT_SCHEMA_VERSION,
|
|
129
|
+
field_names=_FIELD_STATE_NAMES,
|
|
130
|
+
complex_field_names=tuple(complex_field_names),
|
|
131
|
+
cpml_state_names=tuple(cpml_state_names),
|
|
132
|
+
tfsf_auxiliary_state_names=tuple(tfsf_auxiliary_state_names),
|
|
133
|
+
dispersive_state_names=tuple(dispersive_state_names),
|
|
134
|
+
magnetic_dispersive_state_names=tuple(magnetic_dispersive_state_names),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def checkpoint_state_names(solver) -> tuple[str, ...]:
|
|
139
|
+
return checkpoint_schema(solver).state_names
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@dataclass(frozen=True)
|
|
143
|
+
class FDTDCheckpointState:
|
|
144
|
+
step: int
|
|
145
|
+
schema: FDTDCheckpointSchema
|
|
146
|
+
tensors: dict[str, torch.Tensor]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def validate_checkpoint_state(
|
|
150
|
+
state: FDTDCheckpointState,
|
|
151
|
+
*,
|
|
152
|
+
expected_schema: FDTDCheckpointSchema | None = None,
|
|
153
|
+
) -> FDTDCheckpointSchema:
|
|
154
|
+
state_names = tuple(state.tensors.keys())
|
|
155
|
+
if state_names != state.schema.state_names:
|
|
156
|
+
raise RuntimeError(
|
|
157
|
+
"Checkpoint tensor layout drifted from the frozen adjoint state schema. "
|
|
158
|
+
f"Expected {state.schema.state_names}, got {state_names}."
|
|
159
|
+
)
|
|
160
|
+
if expected_schema is not None and state.schema != expected_schema:
|
|
161
|
+
raise RuntimeError(
|
|
162
|
+
"Checkpoint schema mismatch detected during adjoint replay. "
|
|
163
|
+
f"Expected version {expected_schema.version} with names {expected_schema.state_names}, "
|
|
164
|
+
f"got version {state.schema.version} with names {state.schema.state_names}."
|
|
165
|
+
)
|
|
166
|
+
return state.schema
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def capture_checkpoint_state(solver, step: int) -> FDTDCheckpointState:
|
|
170
|
+
schema = checkpoint_schema(solver)
|
|
171
|
+
tensors = {
|
|
172
|
+
name: getattr(solver, name).detach().clone()
|
|
173
|
+
for name in _FIELD_STATE_NAMES
|
|
174
|
+
}
|
|
175
|
+
if bool(getattr(solver, "complex_fields_enabled", False)):
|
|
176
|
+
tensors.update(
|
|
177
|
+
{
|
|
178
|
+
name: getattr(solver, name).detach().clone()
|
|
179
|
+
for name in _COMPLEX_FIELD_STATE_NAMES
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
if getattr(solver, "uses_cpml", False):
|
|
183
|
+
tensors.update(
|
|
184
|
+
{
|
|
185
|
+
name: expand_cpml_memory_tensor(solver, name).detach().clone()
|
|
186
|
+
for name in schema.cpml_state_names
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
190
|
+
tfsf_state = getattr(solver, "_tfsf_state", None)
|
|
191
|
+
auxiliary_grid = None if tfsf_state is None else tfsf_state.get("auxiliary_grid")
|
|
192
|
+
if auxiliary_grid is not None:
|
|
193
|
+
tensors["tfsf_aux_electric"] = auxiliary_grid.electric.detach().clone()
|
|
194
|
+
tensors["tfsf_aux_magnetic"] = auxiliary_grid.magnetic.detach().clone()
|
|
195
|
+
for component_name, model_name, index, tensor_names, entry in iter_dispersive_state_specs(solver) or ():
|
|
196
|
+
for tensor_name in tensor_names:
|
|
197
|
+
tensors[dispersive_state_name(component_name, model_name, index, tensor_name)] = (
|
|
198
|
+
entry[tensor_name].detach().clone()
|
|
199
|
+
)
|
|
200
|
+
if bool(getattr(solver, "complex_fields_enabled", False)):
|
|
201
|
+
for component_name, model_name, index, tensor_names, entry in iter_dispersive_state_specs(solver) or ():
|
|
202
|
+
for tensor_name in tensor_names:
|
|
203
|
+
tensors[dispersive_state_name(component_name, model_name, index, tensor_name) + "_imag"] = (
|
|
204
|
+
entry[f"{tensor_name}_imag"].detach().clone()
|
|
205
|
+
)
|
|
206
|
+
for component_name, model_name, index, tensor_names, entry in iter_magnetic_dispersive_state_specs(solver) or ():
|
|
207
|
+
for tensor_name in tensor_names:
|
|
208
|
+
tensors[dispersive_state_name(component_name, model_name, index, tensor_name)] = (
|
|
209
|
+
entry[tensor_name].detach().clone()
|
|
210
|
+
)
|
|
211
|
+
if bool(getattr(solver, "complex_fields_enabled", False)):
|
|
212
|
+
for component_name, model_name, index, tensor_names, entry in iter_magnetic_dispersive_state_specs(solver) or ():
|
|
213
|
+
for tensor_name in tensor_names:
|
|
214
|
+
tensors[dispersive_state_name(component_name, model_name, index, tensor_name) + "_imag"] = (
|
|
215
|
+
entry[f"{tensor_name}_imag"].detach().clone()
|
|
216
|
+
)
|
|
217
|
+
state = FDTDCheckpointState(step=int(step), schema=schema, tensors=tensors)
|
|
218
|
+
validate_checkpoint_state(state)
|
|
219
|
+
return state
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def clone_checkpoint_tensors(state: FDTDCheckpointState) -> dict[str, torch.Tensor]:
|
|
223
|
+
validate_checkpoint_state(state)
|
|
224
|
+
return {
|
|
225
|
+
name: tensor.detach().clone()
|
|
226
|
+
for name, tensor in state.tensors.items()
|
|
227
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from ..monitors import normalize_component
|
|
6
|
+
from ..scene import prepare_scene
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def component_coords(scene, component: str) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
10
|
+
resolved_scene = prepare_scene(scene)
|
|
11
|
+
component_name = normalize_component(component)
|
|
12
|
+
x, y, z = resolved_scene.x_nodes64, resolved_scene.y_nodes64, resolved_scene.z_nodes64
|
|
13
|
+
x_half, y_half, z_half = resolved_scene.x_half64, resolved_scene.y_half64, resolved_scene.z_half64
|
|
14
|
+
|
|
15
|
+
if component_name == "ex":
|
|
16
|
+
return x_half, y, z
|
|
17
|
+
if component_name == "ey":
|
|
18
|
+
return x, y_half, z
|
|
19
|
+
if component_name == "ez":
|
|
20
|
+
return x, y, z_half
|
|
21
|
+
if component_name == "hx":
|
|
22
|
+
return x, y_half, z_half
|
|
23
|
+
if component_name == "hy":
|
|
24
|
+
return x_half, y, z_half
|
|
25
|
+
if component_name == "hz":
|
|
26
|
+
return x_half, y_half, z
|
|
27
|
+
raise ValueError(f"Unsupported field component: {component!r}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def centered_cell_coords(scene) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
31
|
+
resolved_scene = prepare_scene(scene)
|
|
32
|
+
return resolved_scene.x_half64, resolved_scene.y_half64, resolved_scene.z_half64
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .backend import build_info, debug_linear_indices, get_compiled_extension, get_native_fdtd_module, is_available
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"build_info",
|
|
7
|
+
"debug_linear_indices",
|
|
8
|
+
"get_compiled_extension",
|
|
9
|
+
"get_native_fdtd_module",
|
|
10
|
+
"is_available",
|
|
11
|
+
]
|