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,121 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
from ..visualization import (
|
|
5
|
+
extract_orthogonal_slice,
|
|
6
|
+
plot_cross_section_panels,
|
|
7
|
+
plot_orthogonal_views,
|
|
8
|
+
)
|
|
9
|
+
from .postprocess import (
|
|
10
|
+
get_centered_permittivity,
|
|
11
|
+
get_frequency_solution,
|
|
12
|
+
interpolate_yee_to_center,
|
|
13
|
+
)
|
|
14
|
+
from .coords import component_coords
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _to_numpy_field(value):
|
|
18
|
+
if isinstance(value, torch.Tensor):
|
|
19
|
+
return value.detach().cpu().numpy()
|
|
20
|
+
return np.asarray(value)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def plot_cross_section(
|
|
24
|
+
solver,
|
|
25
|
+
axis="z",
|
|
26
|
+
position=0.0,
|
|
27
|
+
component="abs",
|
|
28
|
+
field_log_scale=False,
|
|
29
|
+
figsize=(12, 5),
|
|
30
|
+
save_path=None,
|
|
31
|
+
verbose=True,
|
|
32
|
+
):
|
|
33
|
+
freq_solution = get_frequency_solution(solver)
|
|
34
|
+
|
|
35
|
+
ex = _to_numpy_field(freq_solution["Ex"])
|
|
36
|
+
ey = _to_numpy_field(freq_solution["Ey"])
|
|
37
|
+
ez = _to_numpy_field(freq_solution["Ez"])
|
|
38
|
+
component_str = component.lower()
|
|
39
|
+
|
|
40
|
+
if component_str == "abs":
|
|
41
|
+
field_data, x_coords, y_coords, z_coords = interpolate_yee_to_center(solver, freq_solution)
|
|
42
|
+
title_prefix = "|E|"
|
|
43
|
+
eps_np = get_centered_permittivity(solver).cpu().numpy()
|
|
44
|
+
elif component_str == "ex":
|
|
45
|
+
field_data = np.abs(ex)
|
|
46
|
+
title_prefix = "|Ex|"
|
|
47
|
+
x_coords, y_coords, z_coords = component_coords(solver.scene, "Ex")
|
|
48
|
+
eps_np = (solver.eps_Ex / solver.eps0).cpu().numpy()
|
|
49
|
+
elif component_str == "ey":
|
|
50
|
+
field_data = np.abs(ey)
|
|
51
|
+
title_prefix = "|Ey|"
|
|
52
|
+
x_coords, y_coords, z_coords = component_coords(solver.scene, "Ey")
|
|
53
|
+
eps_np = (solver.eps_Ey / solver.eps0).cpu().numpy()
|
|
54
|
+
elif component_str == "ez":
|
|
55
|
+
field_data = np.abs(ez)
|
|
56
|
+
title_prefix = "|Ez|"
|
|
57
|
+
x_coords, y_coords, z_coords = component_coords(solver.scene, "Ez")
|
|
58
|
+
eps_np = (solver.eps_Ez / solver.eps0).cpu().numpy()
|
|
59
|
+
else:
|
|
60
|
+
field_data, x_coords, y_coords, z_coords = interpolate_yee_to_center(solver, freq_solution)
|
|
61
|
+
title_prefix = "|E|"
|
|
62
|
+
eps_np = get_centered_permittivity(solver).cpu().numpy()
|
|
63
|
+
|
|
64
|
+
field_info = extract_orthogonal_slice(field_data, axis, position, x_coords, y_coords, z_coords)
|
|
65
|
+
eps_info = extract_orthogonal_slice(eps_np, axis, position, x_coords, y_coords, z_coords)
|
|
66
|
+
|
|
67
|
+
field_slice = field_info["slice"]
|
|
68
|
+
if field_log_scale:
|
|
69
|
+
field_slice = np.where(field_slice > 1e-12, field_slice, 1e-12)
|
|
70
|
+
field_max = np.max(field_slice)
|
|
71
|
+
if field_max > 0:
|
|
72
|
+
field_slice = 20 * np.log10(field_slice / field_max)
|
|
73
|
+
cbar_label = f"{title_prefix} (dB)"
|
|
74
|
+
else:
|
|
75
|
+
cbar_label = f"{title_prefix} (V/m)"
|
|
76
|
+
|
|
77
|
+
plot_cross_section_panels(
|
|
78
|
+
field_slice=field_slice,
|
|
79
|
+
permittivity_slice=eps_info["slice"],
|
|
80
|
+
x_coords=field_info["x_coords"],
|
|
81
|
+
y_coords=field_info["y_coords"],
|
|
82
|
+
xlabel=field_info["xlabel"],
|
|
83
|
+
ylabel=field_info["ylabel"],
|
|
84
|
+
field_title=f"{title_prefix} at {axis}={position:.3f}m",
|
|
85
|
+
field_cbar_label=cbar_label,
|
|
86
|
+
permittivity_title=f"Permittivity at {axis}={position:.3f}m",
|
|
87
|
+
figsize=figsize,
|
|
88
|
+
save_path=save_path,
|
|
89
|
+
verbose=verbose,
|
|
90
|
+
contour_levels=[1.5],
|
|
91
|
+
contour_color="cyan",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def plot_isotropic_3views(
|
|
96
|
+
solver,
|
|
97
|
+
position=0.0,
|
|
98
|
+
field_log_scale=True,
|
|
99
|
+
figsize=(18, 5),
|
|
100
|
+
save_path=None,
|
|
101
|
+
vmin_db=-60,
|
|
102
|
+
verbose=True,
|
|
103
|
+
):
|
|
104
|
+
freq_solution = get_frequency_solution(solver)
|
|
105
|
+
field_data, x_coords, y_coords, z_coords = interpolate_yee_to_center(solver, freq_solution)
|
|
106
|
+
eps = get_centered_permittivity(solver).cpu().numpy()
|
|
107
|
+
|
|
108
|
+
plot_orthogonal_views(
|
|
109
|
+
field_data=field_data,
|
|
110
|
+
permittivity=eps,
|
|
111
|
+
x_coords=x_coords,
|
|
112
|
+
y_coords=y_coords,
|
|
113
|
+
z_coords=z_coords,
|
|
114
|
+
position=position,
|
|
115
|
+
field_log_scale=field_log_scale,
|
|
116
|
+
figsize=figsize,
|
|
117
|
+
save_path=save_path,
|
|
118
|
+
vmin_db=vmin_db,
|
|
119
|
+
verbose=verbose,
|
|
120
|
+
suptitle="Total Electric Field Intensity |E| (FDTD)",
|
|
121
|
+
)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import torch
|
|
3
|
+
|
|
4
|
+
from .boundary import combine_complex_spectral_components
|
|
5
|
+
from .coords import centered_cell_coords
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _build_frequency_solution_from_fields(fields, *, sample_count, window_normalization, source_spectrum=None):
|
|
9
|
+
if sample_count == 0:
|
|
10
|
+
raise ValueError("No DFT samples recorded. Run solve() with dft_frequency first.")
|
|
11
|
+
|
|
12
|
+
if window_normalization > 0:
|
|
13
|
+
scale = 2.0 / window_normalization
|
|
14
|
+
else:
|
|
15
|
+
scale = 2.0 / sample_count
|
|
16
|
+
|
|
17
|
+
result = {}
|
|
18
|
+
for component_name in ("Ex", "Ey", "Ez"):
|
|
19
|
+
payload = fields[component_name]
|
|
20
|
+
result[component_name] = combine_complex_spectral_components(
|
|
21
|
+
payload["real"],
|
|
22
|
+
payload["imag"],
|
|
23
|
+
payload["aux_real"],
|
|
24
|
+
payload["aux_imag"],
|
|
25
|
+
) * scale
|
|
26
|
+
if source_spectrum is not None:
|
|
27
|
+
abs_s = np.abs(source_spectrum)
|
|
28
|
+
floor = max(1e-10 * abs_s, 1e-30) if np.isscalar(abs_s) else 1e-30
|
|
29
|
+
safe = source_spectrum if abs_s > floor else floor
|
|
30
|
+
for comp in ("Ex", "Ey", "Ez"):
|
|
31
|
+
result[comp] = result[comp] / safe
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _legacy_frequency_solution(solver):
|
|
36
|
+
fields = {
|
|
37
|
+
"Ex": {
|
|
38
|
+
"real": solver.dft_Ex_real,
|
|
39
|
+
"imag": solver.dft_Ex_imag,
|
|
40
|
+
"aux_real": solver.dft_Ex_aux_real,
|
|
41
|
+
"aux_imag": solver.dft_Ex_aux_imag,
|
|
42
|
+
},
|
|
43
|
+
"Ey": {
|
|
44
|
+
"real": solver.dft_Ey_real,
|
|
45
|
+
"imag": solver.dft_Ey_imag,
|
|
46
|
+
"aux_real": solver.dft_Ey_aux_real,
|
|
47
|
+
"aux_imag": solver.dft_Ey_aux_imag,
|
|
48
|
+
},
|
|
49
|
+
"Ez": {
|
|
50
|
+
"real": solver.dft_Ez_real,
|
|
51
|
+
"imag": solver.dft_Ez_imag,
|
|
52
|
+
"aux_real": solver.dft_Ez_aux_real,
|
|
53
|
+
"aux_imag": solver.dft_Ez_aux_imag,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
return _build_frequency_solution_from_fields(
|
|
57
|
+
fields,
|
|
58
|
+
sample_count=solver.dft_sample_count,
|
|
59
|
+
window_normalization=solver.dft_window_normalization,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _entry_source_spectrum(entry, scale):
|
|
64
|
+
"""Compute complex source spectrum from a DFT entry's accumulated source DFT."""
|
|
65
|
+
sr = entry.get("source_dft_real", 0.0)
|
|
66
|
+
si = entry.get("source_dft_imag", 0.0)
|
|
67
|
+
return (sr + 1j * si) * scale
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _solver_source_spectrum(solver, entry):
|
|
71
|
+
"""Return source spectrum for an entry if normalize_source is active, else None."""
|
|
72
|
+
if not getattr(solver, '_normalize_source', False):
|
|
73
|
+
return None
|
|
74
|
+
if getattr(solver, '_source_time', None) is None:
|
|
75
|
+
return None
|
|
76
|
+
wn = entry.get("window_normalization", 0.0)
|
|
77
|
+
sc = entry.get("sample_count", 0)
|
|
78
|
+
if wn > 0:
|
|
79
|
+
scale = 2.0 / wn
|
|
80
|
+
elif sc > 0:
|
|
81
|
+
scale = 2.0 / sc
|
|
82
|
+
else:
|
|
83
|
+
return None
|
|
84
|
+
return _entry_source_spectrum(entry, scale)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _resolve_dft_entry(solver, *, frequency=None, freq_index=None):
|
|
88
|
+
if frequency is not None and freq_index is not None:
|
|
89
|
+
raise ValueError("Pass either frequency or freq_index, not both.")
|
|
90
|
+
|
|
91
|
+
entries = getattr(solver, "_dft_entries", ())
|
|
92
|
+
if not entries:
|
|
93
|
+
if frequency is not None or freq_index is not None:
|
|
94
|
+
raise ValueError("Legacy DFT state does not support selecting by frequency or freq_index.")
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
if freq_index is not None:
|
|
98
|
+
index = int(freq_index)
|
|
99
|
+
if index < 0 or index >= len(entries):
|
|
100
|
+
raise IndexError(f"freq_index {index} is out of range for {len(entries)} DFT entries.")
|
|
101
|
+
return entries[index]
|
|
102
|
+
|
|
103
|
+
if frequency is not None:
|
|
104
|
+
target = float(frequency)
|
|
105
|
+
for entry in entries:
|
|
106
|
+
if abs(float(entry["frequency"]) - target) <= max(abs(target), 1.0) * 1e-12:
|
|
107
|
+
return entry
|
|
108
|
+
raise ValueError(f"DFT frequency {target} was not accumulated.")
|
|
109
|
+
|
|
110
|
+
return entries[0]
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def get_frequency_solution(solver, *, frequency=None, freq_index=None, all_frequencies=False):
|
|
114
|
+
if all_frequencies and (frequency is not None or freq_index is not None):
|
|
115
|
+
raise ValueError("all_frequencies cannot be combined with frequency or freq_index.")
|
|
116
|
+
|
|
117
|
+
entries = getattr(solver, "_dft_entries", ())
|
|
118
|
+
if all_frequencies and entries:
|
|
119
|
+
if len(entries) == 1:
|
|
120
|
+
entry = entries[0]
|
|
121
|
+
result = _build_frequency_solution_from_fields(
|
|
122
|
+
entry["fields"],
|
|
123
|
+
sample_count=entry["sample_count"],
|
|
124
|
+
window_normalization=entry["window_normalization"],
|
|
125
|
+
source_spectrum=_solver_source_spectrum(solver, entry),
|
|
126
|
+
)
|
|
127
|
+
if getattr(solver, "verbose", False):
|
|
128
|
+
print(
|
|
129
|
+
f"DFT complete, accumulated {entry['sample_count']} samples, "
|
|
130
|
+
f"window: {solver.dft_window_type}"
|
|
131
|
+
)
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
solutions = [
|
|
135
|
+
_build_frequency_solution_from_fields(
|
|
136
|
+
entry["fields"],
|
|
137
|
+
sample_count=entry["sample_count"],
|
|
138
|
+
window_normalization=entry["window_normalization"],
|
|
139
|
+
source_spectrum=_solver_source_spectrum(solver, entry),
|
|
140
|
+
)
|
|
141
|
+
for entry in entries
|
|
142
|
+
]
|
|
143
|
+
result = {
|
|
144
|
+
"Ex": torch.stack([solution["Ex"] for solution in solutions], dim=0),
|
|
145
|
+
"Ey": torch.stack([solution["Ey"] for solution in solutions], dim=0),
|
|
146
|
+
"Ez": torch.stack([solution["Ez"] for solution in solutions], dim=0),
|
|
147
|
+
"frequencies": tuple(float(entry["frequency"]) for entry in entries),
|
|
148
|
+
}
|
|
149
|
+
if getattr(solver, "verbose", False):
|
|
150
|
+
print(f"DFT complete, accumulated {len(entries)} frequency solutions, window: {solver.dft_window_type}")
|
|
151
|
+
return result
|
|
152
|
+
|
|
153
|
+
entry = _resolve_dft_entry(solver, frequency=frequency, freq_index=freq_index)
|
|
154
|
+
if entry is None:
|
|
155
|
+
result = _legacy_frequency_solution(solver)
|
|
156
|
+
sample_count = solver.dft_sample_count
|
|
157
|
+
else:
|
|
158
|
+
result = _build_frequency_solution_from_fields(
|
|
159
|
+
entry["fields"],
|
|
160
|
+
sample_count=entry["sample_count"],
|
|
161
|
+
window_normalization=entry["window_normalization"],
|
|
162
|
+
source_spectrum=_solver_source_spectrum(solver, entry),
|
|
163
|
+
)
|
|
164
|
+
sample_count = entry["sample_count"]
|
|
165
|
+
|
|
166
|
+
window_type = getattr(solver, "dft_window_type", "none")
|
|
167
|
+
if getattr(solver, "verbose", False):
|
|
168
|
+
print(f"DFT complete, accumulated {sample_count} samples, window: {window_type}")
|
|
169
|
+
return result
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def get_material_permittivity(solver):
|
|
173
|
+
if solver.material_eps_r is not None:
|
|
174
|
+
return solver.material_eps_r
|
|
175
|
+
return solver.scene.permittivity
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def get_centered_permittivity(solver):
|
|
179
|
+
eps = get_material_permittivity(solver)
|
|
180
|
+
return 0.125 * (
|
|
181
|
+
eps[:-1, :-1, :-1] + eps[1:, :-1, :-1] +
|
|
182
|
+
eps[:-1, 1:, :-1] + eps[1:, 1:, :-1] +
|
|
183
|
+
eps[:-1, :-1, 1:] + eps[1:, :-1, 1:] +
|
|
184
|
+
eps[:-1, 1:, 1:] + eps[1:, 1:, 1:]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def interpolate_yee_to_center(solver, freq_solution):
|
|
189
|
+
ex = freq_solution["Ex"]
|
|
190
|
+
ey = freq_solution["Ey"]
|
|
191
|
+
ez = freq_solution["Ez"]
|
|
192
|
+
ex_int = 0.5 * (ex[:, :-1, :] + ex[:, 1:, :])
|
|
193
|
+
ex_int = 0.5 * (ex_int[:, :, :-1] + ex_int[:, :, 1:])
|
|
194
|
+
ey_int = 0.5 * (ey[:-1, :, :] + ey[1:, :, :])
|
|
195
|
+
ey_int = 0.5 * (ey_int[:, :, :-1] + ey_int[:, :, 1:])
|
|
196
|
+
ez_int = 0.5 * (ez[:-1, :, :] + ez[1:, :, :])
|
|
197
|
+
ez_int = 0.5 * (ez_int[:, :-1, :] + ez_int[:, 1:, :])
|
|
198
|
+
if isinstance(ex_int, torch.Tensor):
|
|
199
|
+
field_data = torch.sqrt(torch.abs(ex_int) ** 2 + torch.abs(ey_int) ** 2 + torch.abs(ez_int) ** 2)
|
|
200
|
+
field_data = field_data.detach().cpu().numpy()
|
|
201
|
+
else:
|
|
202
|
+
field_data = np.sqrt(np.abs(ex_int) ** 2 + np.abs(ey_int) ** 2 + np.abs(ez_int) ** 2)
|
|
203
|
+
|
|
204
|
+
x_coords, y_coords, z_coords = centered_cell_coords(solver.scene)
|
|
205
|
+
return field_data, x_coords, y_coords, z_coords
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from .initialization import initialize_solver
|
|
2
|
+
from .materials import (
|
|
3
|
+
advance_magnetic_component_dispersive_state,
|
|
4
|
+
advance_magnetic_dispersive_state,
|
|
5
|
+
advance_component_dispersive_state,
|
|
6
|
+
advance_dispersive_state,
|
|
7
|
+
apply_magnetic_component_dispersive_currents,
|
|
8
|
+
apply_magnetic_dispersive_corrections,
|
|
9
|
+
apply_component_dispersive_currents,
|
|
10
|
+
apply_dispersive_corrections,
|
|
11
|
+
average_node_to_component,
|
|
12
|
+
average_node_to_magnetic_component,
|
|
13
|
+
build_dispersive_templates,
|
|
14
|
+
build_magnetic_dispersive_templates,
|
|
15
|
+
build_materials,
|
|
16
|
+
build_update_coefficients,
|
|
17
|
+
initialize_dispersive_state,
|
|
18
|
+
initialize_magnetic_dispersive_state,
|
|
19
|
+
update_kerr_electric_curls,
|
|
20
|
+
update_nonlinear_electric_coefficients,
|
|
21
|
+
)
|
|
22
|
+
from .module_cache import get_fdtd_module, require_cuda_scene
|
|
23
|
+
from .spectral import (
|
|
24
|
+
accumulate_dft,
|
|
25
|
+
accumulate_dft_gpu,
|
|
26
|
+
advance_phase,
|
|
27
|
+
build_dft_step_tables,
|
|
28
|
+
compute_spectral_start_step,
|
|
29
|
+
compute_window_weight,
|
|
30
|
+
enable_dft,
|
|
31
|
+
normalize_target_frequencies,
|
|
32
|
+
reset_dft_runtime_state,
|
|
33
|
+
resolve_spectral_window_type,
|
|
34
|
+
source_time_kind,
|
|
35
|
+
sync_dft_entries_from_runtime_state,
|
|
36
|
+
sync_dft_legacy_state,
|
|
37
|
+
sync_dft_primary_runtime_state,
|
|
38
|
+
sync_observer_legacy_state,
|
|
39
|
+
sync_observer_primary_state,
|
|
40
|
+
synchronize_device,
|
|
41
|
+
)
|
|
42
|
+
from .stepping import (
|
|
43
|
+
clamp_field_face,
|
|
44
|
+
clamp_pec_boundaries,
|
|
45
|
+
cpml_layout_params,
|
|
46
|
+
enforce_pec_boundaries,
|
|
47
|
+
init_field,
|
|
48
|
+
iter_cpml_memory_regions,
|
|
49
|
+
solve,
|
|
50
|
+
update_electric_fields,
|
|
51
|
+
update_electric_fields_bloch,
|
|
52
|
+
update_electric_fields_cpml,
|
|
53
|
+
update_electric_fields_cpml_compressed,
|
|
54
|
+
update_electric_fields_cpml_dense,
|
|
55
|
+
update_electric_fields_standard,
|
|
56
|
+
update_magnetic_fields,
|
|
57
|
+
update_magnetic_fields_cpml,
|
|
58
|
+
update_magnetic_fields_cpml_compressed,
|
|
59
|
+
update_magnetic_fields_cpml_dense,
|
|
60
|
+
update_magnetic_fields_standard,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"accumulate_dft",
|
|
65
|
+
"accumulate_dft_gpu",
|
|
66
|
+
"build_dft_step_tables",
|
|
67
|
+
"advance_magnetic_component_dispersive_state",
|
|
68
|
+
"advance_magnetic_dispersive_state",
|
|
69
|
+
"advance_component_dispersive_state",
|
|
70
|
+
"advance_dispersive_state",
|
|
71
|
+
"advance_phase",
|
|
72
|
+
"apply_magnetic_component_dispersive_currents",
|
|
73
|
+
"apply_magnetic_dispersive_corrections",
|
|
74
|
+
"apply_component_dispersive_currents",
|
|
75
|
+
"apply_dispersive_corrections",
|
|
76
|
+
"average_node_to_component",
|
|
77
|
+
"average_node_to_magnetic_component",
|
|
78
|
+
"build_dispersive_templates",
|
|
79
|
+
"build_magnetic_dispersive_templates",
|
|
80
|
+
"build_materials",
|
|
81
|
+
"build_update_coefficients",
|
|
82
|
+
"clamp_field_face",
|
|
83
|
+
"clamp_pec_boundaries",
|
|
84
|
+
"compute_spectral_start_step",
|
|
85
|
+
"cpml_layout_params",
|
|
86
|
+
"enable_dft",
|
|
87
|
+
"enforce_pec_boundaries",
|
|
88
|
+
"get_fdtd_module",
|
|
89
|
+
"init_field",
|
|
90
|
+
"initialize_dispersive_state",
|
|
91
|
+
"initialize_magnetic_dispersive_state",
|
|
92
|
+
"initialize_solver",
|
|
93
|
+
"iter_cpml_memory_regions",
|
|
94
|
+
"normalize_target_frequencies",
|
|
95
|
+
"require_cuda_scene",
|
|
96
|
+
"reset_dft_runtime_state",
|
|
97
|
+
"resolve_spectral_window_type",
|
|
98
|
+
"solve",
|
|
99
|
+
"source_time_kind",
|
|
100
|
+
"sync_dft_entries_from_runtime_state",
|
|
101
|
+
"sync_dft_legacy_state",
|
|
102
|
+
"sync_dft_primary_runtime_state",
|
|
103
|
+
"sync_observer_legacy_state",
|
|
104
|
+
"sync_observer_primary_state",
|
|
105
|
+
"synchronize_device",
|
|
106
|
+
"update_kerr_electric_curls",
|
|
107
|
+
"update_nonlinear_electric_coefficients",
|
|
108
|
+
"update_electric_fields",
|
|
109
|
+
"update_electric_fields_bloch",
|
|
110
|
+
"update_electric_fields_cpml",
|
|
111
|
+
"update_electric_fields_cpml_compressed",
|
|
112
|
+
"update_electric_fields_cpml_dense",
|
|
113
|
+
"update_electric_fields_standard",
|
|
114
|
+
"update_magnetic_fields",
|
|
115
|
+
"update_magnetic_fields_cpml",
|
|
116
|
+
"update_magnetic_fields_cpml_compressed",
|
|
117
|
+
"update_magnetic_fields_cpml_dense",
|
|
118
|
+
"update_magnetic_fields_standard",
|
|
119
|
+
]
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from ...compiler.monitors import compile_fdtd_observers, compile_fdtd_time_observers
|
|
9
|
+
from ...compiler.sources import compile_fdtd_sources
|
|
10
|
+
from ..boundary import DEFAULT_CPML_CONFIG
|
|
11
|
+
from .module_cache import get_fdtd_module, require_cuda_scene
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _pole_parameter_bound(value) -> float:
|
|
15
|
+
"""Scalar upper bound of a pole parameter (per-cell custom poles carry tensors)."""
|
|
16
|
+
if torch.is_tensor(value):
|
|
17
|
+
return float(value.detach().max())
|
|
18
|
+
return float(value)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _material_characteristic_frequency(material) -> float:
|
|
22
|
+
if material is None:
|
|
23
|
+
return 0.0
|
|
24
|
+
|
|
25
|
+
characteristic = 0.0
|
|
26
|
+
for pole in getattr(material, "debye_poles", ()):
|
|
27
|
+
characteristic = max(characteristic, 1.0 / (2.0 * np.pi * float(pole.tau)))
|
|
28
|
+
for pole in getattr(material, "drude_poles", ()):
|
|
29
|
+
characteristic = max(
|
|
30
|
+
characteristic,
|
|
31
|
+
_pole_parameter_bound(pole.plasma_frequency),
|
|
32
|
+
float(pole.gamma),
|
|
33
|
+
)
|
|
34
|
+
for pole in getattr(material, "lorentz_poles", ()):
|
|
35
|
+
characteristic = max(
|
|
36
|
+
characteristic,
|
|
37
|
+
float(pole.resonance_frequency),
|
|
38
|
+
float(pole.gamma),
|
|
39
|
+
)
|
|
40
|
+
for pole in getattr(material, "mu_debye_poles", ()):
|
|
41
|
+
characteristic = max(characteristic, 1.0 / (2.0 * np.pi * float(pole.tau)))
|
|
42
|
+
for pole in getattr(material, "mu_drude_poles", ()):
|
|
43
|
+
characteristic = max(
|
|
44
|
+
characteristic,
|
|
45
|
+
_pole_parameter_bound(pole.plasma_frequency),
|
|
46
|
+
float(pole.gamma),
|
|
47
|
+
)
|
|
48
|
+
for pole in getattr(material, "mu_lorentz_poles", ()):
|
|
49
|
+
characteristic = max(
|
|
50
|
+
characteristic,
|
|
51
|
+
float(pole.resonance_frequency),
|
|
52
|
+
float(pole.gamma),
|
|
53
|
+
)
|
|
54
|
+
# Sheet media (e.g. Graphene) expose their relaxation rate directly.
|
|
55
|
+
characteristic = max(
|
|
56
|
+
characteristic, float(getattr(material, "characteristic_frequency", 0.0) or 0.0)
|
|
57
|
+
)
|
|
58
|
+
return characteristic
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _scene_material_characteristic_frequency(scene) -> float:
|
|
62
|
+
characteristic = 0.0
|
|
63
|
+
for structure in getattr(scene, "structures", ()):
|
|
64
|
+
material = getattr(structure, "material", None)
|
|
65
|
+
characteristic = max(characteristic, _material_characteristic_frequency(material))
|
|
66
|
+
return characteristic
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def initialize_solver(solver, scene, frequency=1e9, absorber_type="cpml", cpml_config=None):
|
|
70
|
+
require_cuda_scene(scene)
|
|
71
|
+
solver.scene = scene
|
|
72
|
+
solver.Nx = scene.Nx
|
|
73
|
+
solver.Ny = scene.Ny
|
|
74
|
+
solver.Nz = scene.Nz
|
|
75
|
+
solver.min_dx = float(scene.dx_primal64.min())
|
|
76
|
+
solver.min_dy = float(scene.dy_primal64.min())
|
|
77
|
+
solver.min_dz = float(scene.dz_primal64.min())
|
|
78
|
+
solver.device = torch.device(scene.device)
|
|
79
|
+
solver.verbose = bool(scene.verbose)
|
|
80
|
+
solver.c = 299792458.0
|
|
81
|
+
solver.mu0 = 4 * np.pi * 1e-7
|
|
82
|
+
solver.eps0 = 1 / (solver.mu0 * solver.c**2)
|
|
83
|
+
solver.plot_interval = 50
|
|
84
|
+
solver.progress_update_interval = 25
|
|
85
|
+
compiled_sources = compile_fdtd_sources(scene, default_frequency=float(frequency))
|
|
86
|
+
solver._compiled_sources = tuple(compiled_sources)
|
|
87
|
+
primary_tfsf_source = next(
|
|
88
|
+
(source for source in solver._compiled_sources if source.get("injection", {}).get("kind") == "tfsf"),
|
|
89
|
+
None,
|
|
90
|
+
)
|
|
91
|
+
solver._compiled_source = primary_tfsf_source or (
|
|
92
|
+
solver._compiled_sources[0] if solver._compiled_sources else None
|
|
93
|
+
)
|
|
94
|
+
solver._source_time = None if solver._compiled_source is None else solver._compiled_source["source_time"]
|
|
95
|
+
time_step_frequency = (
|
|
96
|
+
max(
|
|
97
|
+
float(source["source_time"].get("characteristic_frequency", source["source_time"]["frequency"]))
|
|
98
|
+
for source in solver._compiled_sources
|
|
99
|
+
)
|
|
100
|
+
if solver._compiled_sources
|
|
101
|
+
else float(frequency)
|
|
102
|
+
)
|
|
103
|
+
time_step_frequency = max(time_step_frequency, _scene_material_characteristic_frequency(scene))
|
|
104
|
+
solver.source_frequency = (
|
|
105
|
+
float(solver._source_time["frequency"])
|
|
106
|
+
if solver._source_time is not None
|
|
107
|
+
else float(frequency)
|
|
108
|
+
)
|
|
109
|
+
solver.source_omega = 2 * np.pi * solver.source_frequency
|
|
110
|
+
solver.absorber_type = absorber_type.lower()
|
|
111
|
+
solver.boundary_kind = scene.boundary.kind
|
|
112
|
+
solver.active_absorber_type = "none"
|
|
113
|
+
solver.complex_fields_enabled = False
|
|
114
|
+
solver.cpml_config = dict(DEFAULT_CPML_CONFIG)
|
|
115
|
+
if cpml_config is not None:
|
|
116
|
+
solver.cpml_config.update(cpml_config)
|
|
117
|
+
solver._cpml_memory_mode_requested = str(solver.cpml_config.get("memory_mode", "auto")).strip().lower()
|
|
118
|
+
solver._cpml_memory_mode = "none"
|
|
119
|
+
solver._cpml_memory_layouts = {}
|
|
120
|
+
solver._cpml_layout_cache = {}
|
|
121
|
+
solver._cpml_dense_memory_bytes = 0
|
|
122
|
+
solver._cpml_slab_memory_bytes = 0
|
|
123
|
+
solver._cpml_allocated_memory_bytes = 0
|
|
124
|
+
solver._cpml_dense_memory_limit_bytes = None
|
|
125
|
+
solver._cpml_auto_free_bytes = None
|
|
126
|
+
|
|
127
|
+
solver.dt = solver.auto_dt(solver.min_dx, solver.min_dy, solver.min_dz, time_step_frequency)
|
|
128
|
+
# Per-axis spacing arrays: `_e` scales backward diffs in E updates (dual
|
|
129
|
+
# spacing, length N); `_h` scales forward diffs in H updates (primal
|
|
130
|
+
# spacing, length N-1). Reciprocals are computed in float64 then cast.
|
|
131
|
+
solver.inv_dx_e = torch.tensor(1.0 / scene.dx_dual64, dtype=torch.float32, device=solver.device)
|
|
132
|
+
solver.inv_dy_e = torch.tensor(1.0 / scene.dy_dual64, dtype=torch.float32, device=solver.device)
|
|
133
|
+
solver.inv_dz_e = torch.tensor(1.0 / scene.dz_dual64, dtype=torch.float32, device=solver.device)
|
|
134
|
+
solver.inv_dx_h = torch.tensor(1.0 / scene.dx_primal64, dtype=torch.float32, device=solver.device)
|
|
135
|
+
solver.inv_dy_h = torch.tensor(1.0 / scene.dy_primal64, dtype=torch.float32, device=solver.device)
|
|
136
|
+
solver.inv_dz_h = torch.tensor(1.0 / scene.dz_primal64, dtype=torch.float32, device=solver.device)
|
|
137
|
+
solver.fdtd_module = get_fdtd_module()
|
|
138
|
+
|
|
139
|
+
solver.dft_enabled = False
|
|
140
|
+
solver.dft_frequency = None
|
|
141
|
+
solver.dft_frequencies = ()
|
|
142
|
+
solver.dft_Ex_real = None
|
|
143
|
+
solver.dft_Ex_imag = None
|
|
144
|
+
solver.dft_Ey_real = None
|
|
145
|
+
solver.dft_Ey_imag = None
|
|
146
|
+
solver.dft_Ez_real = None
|
|
147
|
+
solver.dft_Ez_imag = None
|
|
148
|
+
solver.dft_Ex_aux_real = None
|
|
149
|
+
solver.dft_Ex_aux_imag = None
|
|
150
|
+
solver.dft_Ey_aux_real = None
|
|
151
|
+
solver.dft_Ey_aux_imag = None
|
|
152
|
+
solver.dft_Ez_aux_real = None
|
|
153
|
+
solver.dft_Ez_aux_imag = None
|
|
154
|
+
solver.dft_sample_count = 0
|
|
155
|
+
solver.dft_sample_counts = ()
|
|
156
|
+
solver.dft_start_step = None
|
|
157
|
+
solver.dft_start_steps = ()
|
|
158
|
+
solver.dft_end_step = None
|
|
159
|
+
solver.dft_end_steps = ()
|
|
160
|
+
solver.dft_window_normalization = 0.0
|
|
161
|
+
solver.dft_window_normalizations = ()
|
|
162
|
+
solver.dft_phase_step_cos = None
|
|
163
|
+
solver.dft_phase_step_sin = None
|
|
164
|
+
solver._dft_entries = []
|
|
165
|
+
solver._dft_batched_fields = {}
|
|
166
|
+
solver._dft_phase_cos = None
|
|
167
|
+
solver._dft_phase_sin = None
|
|
168
|
+
solver._dft_phase_step_cos_values = None
|
|
169
|
+
solver._dft_phase_step_sin_values = None
|
|
170
|
+
solver._dft_start_steps = None
|
|
171
|
+
solver._dft_end_steps = None
|
|
172
|
+
solver._dft_window_normalization_values = None
|
|
173
|
+
solver._dft_sample_count_values = None
|
|
174
|
+
solver._dft_source_dft_real_values = None
|
|
175
|
+
solver._dft_source_dft_imag_values = None
|
|
176
|
+
|
|
177
|
+
solver._source_terms = None
|
|
178
|
+
solver._magnetic_source_terms = None
|
|
179
|
+
solver._electric_source_terms = None
|
|
180
|
+
solver.material_eps_r = None
|
|
181
|
+
solver.material_mu_r = None
|
|
182
|
+
solver._compiled_material_model = None
|
|
183
|
+
solver.dispersive_enabled = False
|
|
184
|
+
solver.electric_dispersive_enabled = False
|
|
185
|
+
solver.magnetic_dispersive_enabled = False
|
|
186
|
+
solver._dispersive_templates = {}
|
|
187
|
+
solver._magnetic_dispersive_templates = {}
|
|
188
|
+
solver.kerr_enabled = False
|
|
189
|
+
solver.chi2_enabled = False
|
|
190
|
+
solver.tpa_enabled = False
|
|
191
|
+
solver.nonlinear_enabled = False
|
|
192
|
+
solver.nonlinear_general_enabled = False
|
|
193
|
+
solver.kerr_chi3 = None
|
|
194
|
+
solver.kerr_chi3_Ex = None
|
|
195
|
+
solver.kerr_chi3_Ey = None
|
|
196
|
+
solver.kerr_chi3_Ez = None
|
|
197
|
+
solver.nonlinear_chi2_Ex = None
|
|
198
|
+
solver.nonlinear_chi2_Ey = None
|
|
199
|
+
solver.nonlinear_chi2_Ez = None
|
|
200
|
+
solver.tpa_sigma_Ex = None
|
|
201
|
+
solver.tpa_sigma_Ey = None
|
|
202
|
+
solver.tpa_sigma_Ez = None
|
|
203
|
+
solver.cex_curl_dynamic = None
|
|
204
|
+
solver.cey_curl_dynamic = None
|
|
205
|
+
solver.cez_curl_dynamic = None
|
|
206
|
+
solver.cex_decay_dynamic = None
|
|
207
|
+
solver.cey_decay_dynamic = None
|
|
208
|
+
solver.cez_decay_dynamic = None
|
|
209
|
+
solver.cex_decay_external = None
|
|
210
|
+
solver.cey_decay_external = None
|
|
211
|
+
solver.cez_decay_external = None
|
|
212
|
+
solver._point_observer_groups = {}
|
|
213
|
+
solver._plane_observer_groups = {}
|
|
214
|
+
solver.last_solve_elapsed_s = None
|
|
215
|
+
solver.tfsf_enabled = False
|
|
216
|
+
solver._tfsf_state = None
|
|
217
|
+
|
|
218
|
+
solver.observers = compile_fdtd_observers(scene)
|
|
219
|
+
solver.observers_enabled = False
|
|
220
|
+
solver.time_observers = compile_fdtd_time_observers(scene)
|
|
221
|
+
solver.time_observers_enabled = False
|
|
222
|
+
solver.observer_frequency = None
|
|
223
|
+
solver.observer_frequencies = ()
|
|
224
|
+
solver.observer_window_type = "hanning"
|
|
225
|
+
solver.observer_start_step = None
|
|
226
|
+
solver.observer_start_steps = ()
|
|
227
|
+
solver.observer_end_step = None
|
|
228
|
+
solver.observer_end_steps = ()
|
|
229
|
+
solver.observer_window_normalization = 0.0
|
|
230
|
+
solver.observer_sample_count = 0
|
|
231
|
+
solver.observer_sample_counts = ()
|
|
232
|
+
solver.observer_phase_step_cos = None
|
|
233
|
+
solver.observer_phase_step_sin = None
|
|
234
|
+
solver._observer_spectral_entries = []
|
|
235
|
+
solver._observer_point_groups_by_frequency = []
|
|
236
|
+
solver._observer_plane_groups_by_frequency = []
|