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.
Files changed (107) hide show
  1. witwin/maxwell/__init__.py +164 -0
  2. witwin/maxwell/adapters/__init__.py +1 -0
  3. witwin/maxwell/adapters/gds.py +203 -0
  4. witwin/maxwell/adapters/tidy3d.py +1669 -0
  5. witwin/maxwell/adjoint_inputs.py +70 -0
  6. witwin/maxwell/compiler/__init__.py +11 -0
  7. witwin/maxwell/compiler/materials.py +1355 -0
  8. witwin/maxwell/compiler/monitors.py +164 -0
  9. witwin/maxwell/compiler/sources.py +222 -0
  10. witwin/maxwell/fdfd/__init__.py +3 -0
  11. witwin/maxwell/fdfd/adjoint/__init__.py +3 -0
  12. witwin/maxwell/fdfd/adjoint/bridge.py +180 -0
  13. witwin/maxwell/fdfd/ams.py +300 -0
  14. witwin/maxwell/fdfd/krylov.py +280 -0
  15. witwin/maxwell/fdfd/plotting.py +110 -0
  16. witwin/maxwell/fdfd/postprocess.py +33 -0
  17. witwin/maxwell/fdfd/solver.py +1057 -0
  18. witwin/maxwell/fdtd/__init__.py +4 -0
  19. witwin/maxwell/fdtd/adjoint/__init__.py +21 -0
  20. witwin/maxwell/fdtd/adjoint/bridge.py +746 -0
  21. witwin/maxwell/fdtd/adjoint/capabilities.py +29 -0
  22. witwin/maxwell/fdtd/adjoint/core.py +3614 -0
  23. witwin/maxwell/fdtd/adjoint/dispatch.py +684 -0
  24. witwin/maxwell/fdtd/adjoint/native.py +2561 -0
  25. witwin/maxwell/fdtd/adjoint/profiler.py +195 -0
  26. witwin/maxwell/fdtd/adjoint/reverse_common.py +207 -0
  27. witwin/maxwell/fdtd/adjoint/seeds.py +524 -0
  28. witwin/maxwell/fdtd/boundary/__init__.py +37 -0
  29. witwin/maxwell/fdtd/boundary/bloch.py +90 -0
  30. witwin/maxwell/fdtd/boundary/common.py +41 -0
  31. witwin/maxwell/fdtd/boundary/cpml.py +527 -0
  32. witwin/maxwell/fdtd/boundary/runtime.py +209 -0
  33. witwin/maxwell/fdtd/checkpoint.py +227 -0
  34. witwin/maxwell/fdtd/coords.py +32 -0
  35. witwin/maxwell/fdtd/cuda/__init__.py +11 -0
  36. witwin/maxwell/fdtd/cuda/backend.py +3532 -0
  37. witwin/maxwell/fdtd/cuda/build.py +292 -0
  38. witwin/maxwell/fdtd/cuda/extension.cpp +2053 -0
  39. witwin/maxwell/fdtd/cuda/kernels/adjoint.cu +5290 -0
  40. witwin/maxwell/fdtd/cuda/kernels/aniso.cu +1704 -0
  41. witwin/maxwell/fdtd/cuda/kernels/boundary.cu +424 -0
  42. witwin/maxwell/fdtd/cuda/kernels/common.cu +74 -0
  43. witwin/maxwell/fdtd/cuda/kernels/common.cuh +82 -0
  44. witwin/maxwell/fdtd/cuda/kernels/dispersive.cu +782 -0
  45. witwin/maxwell/fdtd/cuda/kernels/electric.cu +3613 -0
  46. witwin/maxwell/fdtd/cuda/kernels/magnetic.cu +781 -0
  47. witwin/maxwell/fdtd/cuda/kernels/observers.cu +347 -0
  48. witwin/maxwell/fdtd/cuda/kernels/projection.cu +211 -0
  49. witwin/maxwell/fdtd/cuda/kernels/sources.cu +1482 -0
  50. witwin/maxwell/fdtd/cuda/kernels/spectral.cu +297 -0
  51. witwin/maxwell/fdtd/cuda/launch.h +23 -0
  52. witwin/maxwell/fdtd/cuda/prebuilt/witwin_maxwell_fdtd_cuda.pyd +0 -0
  53. witwin/maxwell/fdtd/cuda/runtime/__init__.py +6 -0
  54. witwin/maxwell/fdtd/cuda/runtime/dispatcher.py +6 -0
  55. witwin/maxwell/fdtd/cuda/runtime/graph.py +33 -0
  56. witwin/maxwell/fdtd/cuda/tensors.h +35 -0
  57. witwin/maxwell/fdtd/dispersion.py +48 -0
  58. witwin/maxwell/fdtd/evaluation.py +290 -0
  59. witwin/maxwell/fdtd/excitation/__init__.py +27 -0
  60. witwin/maxwell/fdtd/excitation/injection.py +1102 -0
  61. witwin/maxwell/fdtd/excitation/modes.py +1675 -0
  62. witwin/maxwell/fdtd/excitation/spatial.py +469 -0
  63. witwin/maxwell/fdtd/excitation/temporal.py +510 -0
  64. witwin/maxwell/fdtd/excitation/tfsf_apply.py +264 -0
  65. witwin/maxwell/fdtd/excitation/tfsf_common.py +421 -0
  66. witwin/maxwell/fdtd/excitation/tfsf_specs.py +490 -0
  67. witwin/maxwell/fdtd/excitation/tfsf_state.py +778 -0
  68. witwin/maxwell/fdtd/io.py +31 -0
  69. witwin/maxwell/fdtd/material_pullback.py +371 -0
  70. witwin/maxwell/fdtd/meshing/__init__.py +3 -0
  71. witwin/maxwell/fdtd/meshing/autogrid.py +335 -0
  72. witwin/maxwell/fdtd/observers.py +1453 -0
  73. witwin/maxwell/fdtd/plotting.py +121 -0
  74. witwin/maxwell/fdtd/postprocess.py +205 -0
  75. witwin/maxwell/fdtd/runtime/__init__.py +119 -0
  76. witwin/maxwell/fdtd/runtime/initialization.py +236 -0
  77. witwin/maxwell/fdtd/runtime/materials.py +1563 -0
  78. witwin/maxwell/fdtd/runtime/module_cache.py +31 -0
  79. witwin/maxwell/fdtd/runtime/spectral.py +581 -0
  80. witwin/maxwell/fdtd/runtime/stepping.py +1987 -0
  81. witwin/maxwell/fdtd/solver.py +502 -0
  82. witwin/maxwell/geometry/__init__.py +5 -0
  83. witwin/maxwell/geometry/polyslab.py +299 -0
  84. witwin/maxwell/materials.py +3 -0
  85. witwin/maxwell/media.py +1467 -0
  86. witwin/maxwell/monitors.py +743 -0
  87. witwin/maxwell/ports.py +113 -0
  88. witwin/maxwell/postprocess/RCS.py +139 -0
  89. witwin/maxwell/postprocess/__init__.py +42 -0
  90. witwin/maxwell/postprocess/diffraction.py +219 -0
  91. witwin/maxwell/postprocess/directivity.py +152 -0
  92. witwin/maxwell/postprocess/emission.py +67 -0
  93. witwin/maxwell/postprocess/modal.py +324 -0
  94. witwin/maxwell/postprocess/nfft.py +266 -0
  95. witwin/maxwell/postprocess/scattering_parameters.py +358 -0
  96. witwin/maxwell/postprocess/stratton_chu.py +1193 -0
  97. witwin/maxwell/result.py +967 -0
  98. witwin/maxwell/scene.py +1338 -0
  99. witwin/maxwell/simulation.py +659 -0
  100. witwin/maxwell/sources.py +917 -0
  101. witwin/maxwell/visualization/__init__.py +28 -0
  102. witwin/maxwell/visualization/interactive.py +484 -0
  103. witwin/maxwell/visualization/plots.py +568 -0
  104. witwin_maxwell-0.3.0.dist-info/METADATA +184 -0
  105. witwin_maxwell-0.3.0.dist-info/RECORD +107 -0
  106. witwin_maxwell-0.3.0.dist-info/WHEEL +4 -0
  107. 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 = []