torchfdtd 1.1.7__py3-none-any.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 (166) hide show
  1. torchfdtd/__init__.py +206 -0
  2. torchfdtd/adjoint_batch.py +333 -0
  3. torchfdtd/adjoint_memory.py +268 -0
  4. torchfdtd/adjoint_planes.py +280 -0
  5. torchfdtd/adjoint_spectrum.py +97 -0
  6. torchfdtd/angular_spectrum.py +516 -0
  7. torchfdtd/anisotropy.py +418 -0
  8. torchfdtd/anisotropy_dispersive.py +395 -0
  9. torchfdtd/batch.py +344 -0
  10. torchfdtd/boundaries.py +443 -0
  11. torchfdtd/capabilities.json +13330 -0
  12. torchfdtd/capabilities.py +882 -0
  13. torchfdtd/cli.py +160 -0
  14. torchfdtd/convergence.py +178 -0
  15. torchfdtd/cuda_adjoint.py +228 -0
  16. torchfdtd/cuda_batch.py +170 -0
  17. torchfdtd/cuda_bootstrap.py +57 -0
  18. torchfdtd/cuda_complex.py +101 -0
  19. torchfdtd/cuda_complex_adjoint.py +149 -0
  20. torchfdtd/cuda_diagnostics.py +135 -0
  21. torchfdtd/cuda_dispersive_adjoint.py +230 -0
  22. torchfdtd/cuda_graph.py +57 -0
  23. torchfdtd/cuda_kernels.py +348 -0
  24. torchfdtd/cuda_memory.py +78 -0
  25. torchfdtd/cuda_monitors.py +156 -0
  26. torchfdtd/cuda_subpixel.py +42 -0
  27. torchfdtd/cuda_tfsf.py +82 -0
  28. torchfdtd/density_layer.py +126 -0
  29. torchfdtd/design.py +123 -0
  30. torchfdtd/design_checkpoint.py +139 -0
  31. torchfdtd/design_parameterization.py +209 -0
  32. torchfdtd/design_problem.py +314 -0
  33. torchfdtd/design_service.py +106 -0
  34. torchfdtd/detector_allocation.py +52 -0
  35. torchfdtd/differentiable.py +875 -0
  36. torchfdtd/differentiable_geometry.py +338 -0
  37. torchfdtd/dispersive_adjoint.py +386 -0
  38. torchfdtd/doctor.py +191 -0
  39. torchfdtd/domain_decomposition.py +365 -0
  40. torchfdtd/electron_model.py +99 -0
  41. torchfdtd/endpoint_native.py +228 -0
  42. torchfdtd/endpoint_project.py +196 -0
  43. torchfdtd/execution_modes.py +722 -0
  44. torchfdtd/execution_tuning.py +305 -0
  45. torchfdtd/fabrication.py +245 -0
  46. torchfdtd/farfield_api.py +177 -0
  47. torchfdtd/feature_inventory.json +35036 -0
  48. torchfdtd/field_monitors.py +245 -0
  49. torchfdtd/fsp.py +348 -0
  50. torchfdtd/fsp_binary.py +427 -0
  51. torchfdtd/fsp_geometry.py +296 -0
  52. torchfdtd/fsp_instruments.py +139 -0
  53. torchfdtd/fsp_native.py +829 -0
  54. torchfdtd/fsp_objects.py +141 -0
  55. torchfdtd/fsp_service.py +183 -0
  56. torchfdtd/fsp_settings.py +396 -0
  57. torchfdtd/gds.py +522 -0
  58. torchfdtd/gds_ports.py +166 -0
  59. torchfdtd/gds_service.py +112 -0
  60. torchfdtd/geometry.py +148 -0
  61. torchfdtd/grouped_batch.py +146 -0
  62. torchfdtd/identity.py +102 -0
  63. torchfdtd/information.py +98 -0
  64. torchfdtd/injection.py +141 -0
  65. torchfdtd/material_fit.py +534 -0
  66. torchfdtd/materials.py +193 -0
  67. torchfdtd/memory_accounting.py +378 -0
  68. torchfdtd/memory_profile.py +114 -0
  69. torchfdtd/mesh.py +134 -0
  70. torchfdtd/mode_branches.py +592 -0
  71. torchfdtd/mode_injection.py +386 -0
  72. torchfdtd/mode_network.py +359 -0
  73. torchfdtd/mode_network_project.py +322 -0
  74. torchfdtd/mode_network_service.py +246 -0
  75. torchfdtd/mode_network_worker.py +56 -0
  76. torchfdtd/mode_ports.py +361 -0
  77. torchfdtd/models.py +969 -0
  78. torchfdtd/open_mode_injection.py +171 -0
  79. torchfdtd/open_mode_operators.py +138 -0
  80. torchfdtd/open_mode_ports.py +350 -0
  81. torchfdtd/optical_data.py +91 -0
  82. torchfdtd/oracle_memory.py +136 -0
  83. torchfdtd/periodic_adjoint.py +335 -0
  84. torchfdtd/periodic_design.py +204 -0
  85. torchfdtd/periodic_response.py +85 -0
  86. torchfdtd/plan.py +544 -0
  87. torchfdtd/plane_execution.py +117 -0
  88. torchfdtd/pmc_cpml.py +286 -0
  89. torchfdtd/pmc_cpml_cuda.py +121 -0
  90. torchfdtd/pmc_cuda.py +203 -0
  91. torchfdtd/pmc_reference.py +174 -0
  92. torchfdtd/pmc_simulation.py +269 -0
  93. torchfdtd/polarization.py +82 -0
  94. torchfdtd/ports.py +316 -0
  95. torchfdtd/priorities.py +96 -0
  96. torchfdtd/propagation_api.py +206 -0
  97. torchfdtd/pupil_response.py +36 -0
  98. torchfdtd/radiation.py +668 -0
  99. torchfdtd/radiation_api.py +75 -0
  100. torchfdtd/radiation_box.py +363 -0
  101. torchfdtd/radiation_box_io.py +242 -0
  102. torchfdtd/radiation_io.py +68 -0
  103. torchfdtd/recomputed_batch.py +90 -0
  104. torchfdtd/recorded_execution.py +167 -0
  105. torchfdtd/recorded_observations.py +71 -0
  106. torchfdtd/reference_cache.py +58 -0
  107. torchfdtd/reservation_registry.py +99 -0
  108. torchfdtd/response_cache.py +128 -0
  109. torchfdtd/result_store.py +263 -0
  110. torchfdtd/results.py +481 -0
  111. torchfdtd/reversible.py +278 -0
  112. torchfdtd/reversible_cpml.py +603 -0
  113. torchfdtd/reversible_cpml_kernels.py +355 -0
  114. torchfdtd/reversible_cpml_memory.py +255 -0
  115. torchfdtd/reversible_cpml_planes.py +82 -0
  116. torchfdtd/reversible_cuda_fused.py +434 -0
  117. torchfdtd/reversible_cuda_tuning.py +241 -0
  118. torchfdtd/reversible_lorentz.py +641 -0
  119. torchfdtd/reversible_lorentz_cuda.py +284 -0
  120. torchfdtd/reversible_memory.py +99 -0
  121. torchfdtd/reversible_trace.py +273 -0
  122. torchfdtd/run_control.py +159 -0
  123. torchfdtd/server.py +529 -0
  124. torchfdtd/session.py +488 -0
  125. torchfdtd/solver.py +906 -0
  126. torchfdtd/source_adjoint.py +232 -0
  127. torchfdtd/source_adjoint_cuda.py +73 -0
  128. torchfdtd/source_parameters.py +65 -0
  129. torchfdtd/source_preview.py +229 -0
  130. torchfdtd/spacetime.py +540 -0
  131. torchfdtd/spectra.py +116 -0
  132. torchfdtd/stability_checks.py +100 -0
  133. torchfdtd/staging.py +152 -0
  134. torchfdtd/state_store.py +199 -0
  135. torchfdtd/streamed.py +612 -0
  136. torchfdtd/streamed_cost.py +46 -0
  137. torchfdtd/streamed_density.py +245 -0
  138. torchfdtd/streamed_dispersive.py +244 -0
  139. torchfdtd/streamed_geometry.py +345 -0
  140. torchfdtd/streamed_planning.py +118 -0
  141. torchfdtd/streamed_restart.py +703 -0
  142. torchfdtd/streamed_tensor.py +181 -0
  143. torchfdtd/streamed_tuning.py +404 -0
  144. torchfdtd/streamed_work.py +228 -0
  145. torchfdtd/subpixel.py +206 -0
  146. torchfdtd/subpixel_dispersive.py +426 -0
  147. torchfdtd/subpixel_geometry.py +228 -0
  148. torchfdtd/tensor_batch.py +336 -0
  149. torchfdtd/tensor_native.py +137 -0
  150. torchfdtd/tensor_packet.py +86 -0
  151. torchfdtd/tensor_project.py +147 -0
  152. torchfdtd/tfsf.py +228 -0
  153. torchfdtd/tile_workspace.py +173 -0
  154. torchfdtd/tiled.py +605 -0
  155. torchfdtd/tuning.py +160 -0
  156. torchfdtd/waveforms.py +74 -0
  157. torchfdtd/web/assets/index-BQKZ9PI6.js +4151 -0
  158. torchfdtd/web/assets/index-DErGp_7h.css +1 -0
  159. torchfdtd/web/index.html +5 -0
  160. torchfdtd-1.1.7.dist-info/METADATA +289 -0
  161. torchfdtd-1.1.7.dist-info/RECORD +166 -0
  162. torchfdtd-1.1.7.dist-info/WHEEL +5 -0
  163. torchfdtd-1.1.7.dist-info/entry_points.txt +2 -0
  164. torchfdtd-1.1.7.dist-info/licenses/LICENSE +22 -0
  165. torchfdtd-1.1.7.dist-info/licenses/THIRD_PARTY_NOTICES.txt +132 -0
  166. torchfdtd-1.1.7.dist-info/top_level.txt +1 -0
torchfdtd/__init__.py ADDED
@@ -0,0 +1,206 @@
1
+ """TorchFDTD: micrometre geometry, SI time, open-source Yee FDTD."""
2
+ # The upstream grid package changes process-wide Torch defaults on first import.
3
+ # A forward simulator must not disable gradients in its caller's training code.
4
+ import torch as _torch
5
+ _previous_dtype, _previous_grad = _torch.get_default_dtype(), _torch.is_grad_enabled()
6
+ try:
7
+ import fdtd as _fdtd
8
+ finally:
9
+ _torch.set_default_dtype(_previous_dtype)
10
+ _torch.set_grad_enabled(_previous_grad)
11
+ del _torch, _fdtd, _previous_dtype, _previous_grad
12
+
13
+ from .models import Project, Region, MeshRefinement, Structure, Source, SourceTimeSettings, TimeSignal, Monitor, Material, BoundaryFace, Boundaries, SpectrumSettings
14
+ from .solver import Simulation, Result
15
+ from .session import FDTD
16
+ from .mesh import freeze_refinements
17
+ from .models import FieldMonitor
18
+ from .field_monitors import normalize_flux
19
+ from .batch import BatchCase, BatchItem, BatchReport, BatchRunner, run_batch, parameter_case, parameter_sweep
20
+ from .design import DesignResult, optimize
21
+ from .models import LorentzPole, RunControl
22
+ from .convergence import ConvergenceReport, mesh_refinement_projects, mesh_convergence
23
+
24
+ __all__ = ['Project', 'Region', 'MeshRefinement', 'freeze_refinements', 'Structure', 'Source', 'SourceTimeSettings', 'TimeSignal', 'Monitor', 'FieldMonitor', 'normalize_flux', 'Material', 'BoundaryFace', 'Boundaries', 'SpectrumSettings', 'Simulation', 'Result', 'FDTD']
25
+ __all__ += ['BatchCase','BatchItem','BatchReport','BatchRunner','run_batch','parameter_case','parameter_sweep','DesignResult','optimize']
26
+ __all__ += ['LorentzPole', 'RunControl']
27
+ __all__ += ['ConvergenceReport', 'mesh_refinement_projects', 'mesh_convergence']
28
+ from .tensor_batch import run_tensor_batch
29
+ __all__ += ['run_tensor_batch']
30
+ from .grouped_batch import plan_grouped_batch, run_grouped_batch
31
+ __all__ += ['plan_grouped_batch', 'run_grouped_batch']
32
+ from .tuning import TensorBatchTuning, tune_tensor_batch
33
+ __all__ += ['TensorBatchTuning', 'tune_tensor_batch']
34
+ from .fsp_geometry import write_fsp_geometry, write_fsp_scene
35
+ __all__ += ['write_fsp_geometry', 'write_fsp_scene']
36
+ from .optical_data import OpticalData
37
+ from .material_fit import FitOptions, MaterialFitResult, fit_material, material_fit_report
38
+ __all__ += ['OpticalData', 'FitOptions', 'MaterialFitResult', 'fit_material', 'material_fit_report']
39
+ from .differentiable import AdjointOptions, DifferentiableSimulation, DifferentiableResult, smooth_sphere_epsilon
40
+ __all__ += ['AdjointOptions', 'DifferentiableSimulation', 'DifferentiableResult', 'smooth_sphere_epsilon']
41
+ from .adjoint_memory import estimate_adjoint_memory
42
+ __all__ += ['estimate_adjoint_memory']
43
+ from .adjoint_spectrum import DifferentiableSpectrum
44
+ __all__ += ['DifferentiableSpectrum']
45
+ from .memory_profile import profile_memory_transfers
46
+ __all__ += ['profile_memory_transfers']
47
+ from .streamed import (StreamedAdjointOptions, StreamedSimulation, estimate_streamed_memory,
48
+ StreamedStoragePlan, select_streamed_storage)
49
+ __all__ += ['StreamedAdjointOptions', 'StreamedSimulation', 'estimate_streamed_memory',
50
+ 'StreamedStoragePlan', 'select_streamed_storage']
51
+ from .streamed_tuning import StreamedTuning, tune_streamed, tune_streamed_dispersive
52
+ __all__ += ['StreamedTuning', 'tune_streamed', 'tune_streamed_dispersive']
53
+ from .adjoint_planes import DifferentiablePlaneSimulation, DifferentiablePlaneResult
54
+ __all__ += ['DifferentiablePlaneSimulation', 'DifferentiablePlaneResult']
55
+ from .information import GaussianTargetResult, gaussian_target_information, shot_read_covariance
56
+ __all__ += ['GaussianTargetResult', 'gaussian_target_information', 'shot_read_covariance']
57
+ from .recomputed_batch import recompute_cases
58
+ __all__ += ['recompute_cases']
59
+ from .detector_allocation import quadrant_intensity_allocation
60
+ __all__ += ['quadrant_intensity_allocation']
61
+ from .polarization import calibrate_plane_polarization, mix_plane_fields
62
+ __all__ += ['calibrate_plane_polarization', 'mix_plane_fields']
63
+ from .electron_model import spectral_interpolate, spectral_electron_model, exposure_target_information, SpectralElectronModel, ExposureInformation
64
+ __all__ += ['spectral_interpolate', 'spectral_electron_model', 'exposure_target_information', 'SpectralElectronModel', 'ExposureInformation']
65
+ from .density_layer import periodic_density_layer
66
+ __all__ += ['periodic_density_layer']
67
+ from .pupil_response import spectral_pupil_response
68
+ __all__ += ['spectral_pupil_response']
69
+ from .periodic_response import periodic_layer_response
70
+ __all__ += ['periodic_layer_response']
71
+ from .reference_cache import PlaneReferenceCache
72
+ __all__ += ['PlaneReferenceCache']
73
+ from .dispersive_adjoint import DispersiveSimulation, DispersivePlaneSimulation
74
+ __all__ += ['DispersiveSimulation', 'DispersivePlaneSimulation']
75
+ from .streamed_dispersive import StreamedDispersiveSimulation, estimate_streamed_dispersive_memory
76
+ __all__ += ['StreamedDispersiveSimulation', 'estimate_streamed_dispersive_memory']
77
+ from .execution_tuning import AdjointExecutionPolicy, AdjointExecutionSelection, tune_adjoint_execution
78
+ __all__ += ['AdjointExecutionPolicy', 'AdjointExecutionSelection', 'tune_adjoint_execution']
79
+ from .adjoint_batch import AdjointCase, AdjointBatchOptions, AdjointBatchResult, RecomputedAdjointBatch
80
+ __all__ += ['AdjointCase', 'AdjointBatchOptions', 'AdjointBatchResult', 'RecomputedAdjointBatch']
81
+
82
+ from .periodic_adjoint import PeriodicLayerResponse
83
+ __all__ += ['PeriodicLayerResponse']
84
+
85
+ from .response_cache import PeriodicResponseCache
86
+ __all__ += ['PeriodicResponseCache']
87
+
88
+ from .periodic_design import PeriodicDesignConfig, periodic_design_plan, run_periodic_design
89
+ __all__ += ['PeriodicDesignConfig', 'periodic_design_plan', 'run_periodic_design']
90
+
91
+ from .differentiable_geometry import DifferentiableSolid, smooth_geometry_epsilon, spline_outline
92
+ __all__ += ['DifferentiableSolid', 'smooth_geometry_epsilon', 'spline_outline']
93
+ from .streamed_geometry import (StreamedGeometry, streamed_geometry, StreamedGeometrySimulation,
94
+ StreamedGeometryPlaneSimulation)
95
+ __all__ += ['StreamedGeometry', 'streamed_geometry', 'StreamedGeometrySimulation',
96
+ 'StreamedGeometryPlaneSimulation']
97
+ from .streamed_density import (StreamedDensityLayer, streamed_density_layer, StreamedDensitySimulation,
98
+ StreamedDensityPlaneSimulation)
99
+ __all__ += ['StreamedDensityLayer', 'streamed_density_layer', 'StreamedDensitySimulation',
100
+ 'StreamedDensityPlaneSimulation']
101
+ from .design_parameterization import DensityParameterization
102
+ from .gds import GDSLayer, GDSPortLayer, GDSPort, GDSLimits, GDSImport, import_gds, export_gds
103
+ from .mode_ports import WaveguideMode, solve_waveguide_modes, mode_power_overlap, normalized_mode_power
104
+ from .mode_injection import (ModalLaunch, prepare_modal_launch, ModeInjectedPlaneSimulation,
105
+ modal_plane_amplitudes, modal_s_parameters)
106
+ from .radiation import (DiffractionResult, diffraction_orders, diffraction_efficiency,
107
+ FarFieldResult, project_farfield, normalized_farfield_intensity,
108
+ NearZoneResult, project_nearzone, farfield_at_points,
109
+ spherical_directions, spherical_points, cartesian_plane_points, kspace_directions)
110
+ __all__ += ['DensityParameterization', 'GDSLayer', 'GDSPortLayer', 'GDSPort', 'GDSLimits',
111
+ 'GDSImport', 'import_gds', 'export_gds', 'WaveguideMode', 'solve_waveguide_modes',
112
+ 'mode_power_overlap', 'normalized_mode_power', 'ModalLaunch', 'prepare_modal_launch',
113
+ 'ModeInjectedPlaneSimulation', 'modal_plane_amplitudes', 'modal_s_parameters',
114
+ 'DiffractionResult', 'diffraction_orders', 'diffraction_efficiency', 'FarFieldResult',
115
+ 'project_farfield', 'normalized_farfield_intensity', 'NearZoneResult', 'project_nearzone',
116
+ 'farfield_at_points', 'spherical_directions', 'spherical_points', 'cartesian_plane_points',
117
+ 'kspace_directions']
118
+
119
+ from .anisotropy import TensorDielectricSimulation
120
+ from .tensor_project import TensorProject, tensor_from_project
121
+ from .pmc_simulation import EndpointSimulation
122
+ __all__ += ["TensorDielectricSimulation", "EndpointSimulation"]
123
+ __all__ += ["TensorProject", "tensor_from_project"]
124
+
125
+ from .domain_decomposition import (DistributedYeeDomain, SlabOwnership,
126
+ distributed_capabilities, plan_domain_decomposition)
127
+ __all__ += ["DistributedYeeDomain", "SlabOwnership", "distributed_capabilities",
128
+ "plan_domain_decomposition"]
129
+
130
+ from .endpoint_project import EndpointProject, endpoint_from_project
131
+ from .mode_network import FixedModePort, ModeNetwork, ModeNetworkResult
132
+ __all__ += ["EndpointProject", "endpoint_from_project", "FixedModePort",
133
+ "ModeNetwork", "ModeNetworkResult"]
134
+
135
+ from .pmc_cpml import EndpointCPMLSimulation
136
+ from .radiation_io import native_radiation_plane, load_native_radiation_plane
137
+ __all__ += ["EndpointCPMLSimulation", "native_radiation_plane", "load_native_radiation_plane"]
138
+
139
+ from .reversible import ReversibleOptions, ReversibleSimulation
140
+ __all__ += ["ReversibleOptions", "ReversibleSimulation"]
141
+
142
+ from .reversible_cpml import ReversibleCPMLOptions, ReversibleCPMLSimulation
143
+ __all__ += ["ReversibleCPMLOptions", "ReversibleCPMLSimulation"]
144
+ from .reversible_cpml_planes import ReversibleCPMLPlaneSimulation
145
+ __all__ += ["ReversibleCPMLPlaneSimulation"]
146
+
147
+ from .open_mode_ports import OpenWaveguideMode, solve_open_waveguide_modes
148
+ from .open_mode_injection import OpenPortOptions, OpenModalLaunch, prepare_open_modal_launch
149
+ __all__ += ["OpenWaveguideMode", "solve_open_waveguide_modes", "OpenPortOptions",
150
+ "OpenModalLaunch", "prepare_open_modal_launch"]
151
+
152
+ from .mode_network_project import ModeNetworkConfig, mode_network_plan, run_mode_network
153
+ __all__ += ["ModeNetworkConfig", "mode_network_plan", "run_mode_network"]
154
+
155
+ from .mode_branches import ModePort, ModeBranchNetwork, prepare_aperture_modal_launch, branch_network_from_ports
156
+ __all__ += ["ModePort", "ModeBranchNetwork", "prepare_aperture_modal_launch", "branch_network_from_ports"]
157
+
158
+ from .radiation_box import StoredRadiationBox, native_radiation_box
159
+ from .radiation_box_io import load_native_radiation_box
160
+ __all__ += ["StoredRadiationBox", "native_radiation_box", "load_native_radiation_box"]
161
+
162
+ from .source_adjoint import SourceWaveformSimulation, SourceWaveformPlaneSimulation
163
+ from .source_parameters import gaussian_waveform
164
+ __all__ += ["SourceWaveformSimulation", "SourceWaveformPlaneSimulation", "gaussian_waveform"]
165
+
166
+ from .streamed_work import estimate_streamed_work
167
+ from .streamed_planning import StreamedWorkPlan, plan_streamed_work
168
+ __all__ += ["estimate_streamed_work", "StreamedWorkPlan", "plan_streamed_work"]
169
+
170
+ from .tiled import (TilePlan, TileSpec, StitchedPlane, plan_tiles, run_tiled, stitch_planes, propagate_plane,
171
+ farfield_from_stitched, suggest_overlap, TiledPlaneSimulation)
172
+ __all__ += ["TilePlan", "TileSpec", "StitchedPlane", "plan_tiles", "run_tiled", "stitch_planes", "propagate_plane",
173
+ "farfield_from_stitched", "suggest_overlap", "TiledPlaneSimulation"]
174
+
175
+ from .angular_spectrum import (PlaneSpectrum, SectionResult, VolumeResult, PointsResult, plane_spectrum, propagate_section,
176
+ propagate_volume, propagate_points, volume_bytes)
177
+ __all__ += ["PlaneSpectrum", "SectionResult", "VolumeResult", "PointsResult", "plane_spectrum", "propagate_section",
178
+ "propagate_volume", "propagate_points", "volume_bytes"]
179
+
180
+ from .models import MaterialProvenance
181
+ from .material_fit import (MaterialImportResult, MaterialBandWarning, import_material_table, discretization_report,
182
+ fit_band_extrapolation)
183
+ __all__ += ["MaterialProvenance", "MaterialImportResult", "MaterialBandWarning", "import_material_table",
184
+ "discretization_report", "fit_band_extrapolation"]
185
+ from .source_preview import preview_source, effective_bandwidth
186
+ __all__ += ["preview_source", "effective_bandwidth"]
187
+
188
+ from .material_fit import fit_discrete_lorentz
189
+ __all__ += ["fit_discrete_lorentz"]
190
+ from .results import (ResultRecord, guarded_ratio, reflection_transmission, s_parameters, mode_decomposition,
191
+ diffraction_record, farfield_record, nearzone_record)
192
+ __all__ += ["ResultRecord", "guarded_ratio", "reflection_transmission", "s_parameters", "mode_decomposition",
193
+ "diffraction_record", "farfield_record", "nearzone_record"]
194
+ from .ports import (FixedPortSectionError, ModeTrackingWarning, WeakModeWarning, TrackedPortModes, PortDiagnostics,
195
+ track_port_modes, port_diagnostics, degenerate_clusters, overlap_matrix, confinement_factor,
196
+ shift_reference_plane, deembed_s_matrix, separate_directions, port_normalization, fixed_port_section)
197
+ __all__ += ["FixedPortSectionError", "ModeTrackingWarning", "WeakModeWarning", "TrackedPortModes", "PortDiagnostics",
198
+ "track_port_modes", "port_diagnostics", "degenerate_clusters", "overlap_matrix", "confinement_factor",
199
+ "shift_reference_plane", "deembed_s_matrix", "separate_directions", "port_normalization", "fixed_port_section"]
200
+
201
+ from .density_layer import bounded_density_layer
202
+ from .fabrication import (FeatureSizes, measure_feature_sizes, fabrication_perturbation, binary_structures,
203
+ morphological_open, square_offsets)
204
+ from .design_problem import Continuation, DesignProblem
205
+ __all__ += ["bounded_density_layer", "FeatureSizes", "measure_feature_sizes", "fabrication_perturbation",
206
+ "binary_structures", "morphological_open", "square_offsets", "Continuation", "DesignProblem"]
@@ -0,0 +1,333 @@
1
+ """Shared-budget, sequential case replay for coupled FDTD objectives."""
2
+ from dataclasses import dataclass, replace
3
+ from copy import deepcopy
4
+ import math
5
+ import time
6
+
7
+ import torch
8
+
9
+ from .adjoint_spectrum import SpectralObservation
10
+ from .execution_tuning import AdjointExecutionPolicy, _resident_reservation, _streamed_options
11
+ from .memory_profile import host_memory
12
+ from .plane_execution import plane_mode, plane_reservation
13
+ from .streamed import _reservation
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class AdjointCase:
18
+ """A fixed project and execution policy with explicit material bindings.
19
+
20
+ parameter_indices selects epsilon, or epsilon_inf/strength/omega0/gamma,
21
+ from the batch call's CPU tensors. Shared indices accumulate derivatives.
22
+ Frequencies, mesh, monitors and sources are fixed during each graph.
23
+ Recorded plane cases require an explicit homogeneous fixed exterior value
24
+ in fixed_background_epsilon. Other algorithms reject that argument.
25
+ """
26
+ project: object
27
+ policy: AdjointExecutionPolicy
28
+ parameter_indices: tuple = (0,)
29
+ frequency_hz: object = None
30
+ quadrature_counts: dict | None = None
31
+ block_size: int = 32
32
+ fixed_background_epsilon: float | None = None
33
+
34
+ def __post_init__(self):
35
+ if not isinstance(self.policy,AdjointExecutionPolicy):raise ValueError('Each case needs an AdjointExecutionPolicy.')
36
+ indices=tuple(self.parameter_indices)
37
+ if len(indices) not in (1,4) or any(isinstance(i,bool) or not isinstance(i,int) or i<0 for i in indices):
38
+ raise ValueError('Bind one dielectric or four ADE parameter indices.')
39
+ object.__setattr__(self,'parameter_indices',indices)
40
+ recorded=getattr(self.policy,'recorded',None) is not None
41
+ background=self.fixed_background_epsilon
42
+ if recorded:
43
+ if type(background) is not float or not math.isfinite(background) or background<1:
44
+ raise ValueError('Recorded cases require finite Python float fixed_background_epsilon >= 1.')
45
+ if len(indices)!=1 or not plane_mode(self.project):
46
+ raise ValueError('Recorded cases support nondispersive field planes only.')
47
+ elif background is not None:
48
+ raise ValueError('fixed_background_epsilon requires a recorded execution policy.')
49
+ if isinstance(self.block_size,bool) or not isinstance(self.block_size,int) or self.block_size<1:
50
+ raise ValueError('block_size must be a positive integer.')
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class AdjointBatchOptions:
55
+ """Total solver-owned budgets, with CPU observations and accumulators.
56
+
57
+ Geometry/input storage, optimizer and user objective graphs, CUDA context,
58
+ unrelated allocations and OS file cache are outside these budgets.
59
+ """
60
+ host_budget_bytes: int = 8*1024**3
61
+ gpu_budget_bytes: int = 1024**3
62
+ disk_budget_bytes: int | None = None
63
+ output_budget_bytes: int = 64*1024**2
64
+ replay_rtol: float = 1e-6
65
+ replay_atol: float = 0.
66
+
67
+ def __post_init__(self):
68
+ for name in ('host_budget_bytes','gpu_budget_bytes','disk_budget_bytes','output_budget_bytes'):
69
+ value=getattr(self,name)
70
+ if name=='disk_budget_bytes' and value is None:continue
71
+ if isinstance(value,bool) or not isinstance(value,int) or value<1:
72
+ raise ValueError(f'{name} must be a positive integer byte count.')
73
+ if any(not math.isfinite(v) or v<0 for v in (self.replay_rtol,self.replay_atol)):
74
+ raise ValueError('Replay tolerances must be finite and nonnegative.')
75
+
76
+
77
+ @dataclass
78
+ class AdjointBatchResult:
79
+ cases: tuple
80
+ report: dict
81
+
82
+
83
+ def _tensors(result):
84
+ if isinstance(result,dict):return tuple(p.fields for p in result.values())
85
+ return (result.fields if hasattr(result,'fields') else result.signals,)
86
+
87
+
88
+ def _replace_tensors(result,values):
89
+ if isinstance(result,dict):return {key:replace(plane,fields=value) for (key,plane),value in zip(result.items(),values)}
90
+ return replace(result,**{'fields' if hasattr(result,'fields') else 'signals':values[0]})
91
+
92
+
93
+ class _PreparedCase:
94
+ def __init__(self,spec):
95
+ self.spec=spec
96
+ self.dispersive=len(spec.parameter_indices)==4
97
+ self.planes=plane_mode(spec.project)
98
+ self.configuration=deepcopy((spec.policy,spec.fixed_background_epsilon,
99
+ spec.parameter_indices,spec.block_size,spec.quadrature_counts))
100
+ settings=dict(dispersive=self.dispersive,quadrature_counts=spec.quadrature_counts)
101
+ if getattr(spec.policy,'recorded',None) is not None:
102
+ settings['fixed_background_epsilon']=spec.fixed_background_epsilon
103
+ self.model=spec.policy.simulation(spec.project,**settings)
104
+ self.project=self.model.project
105
+ self.snapshot=self.project.model_dump()
106
+ self.spectral=None
107
+ scalar=torch.empty((),dtype=getattr(torch,self.project.region.precision),device='cpu')
108
+ if self.planes:
109
+ if spec.frequency_hz is None:raise ValueError('Plane cases require frequency_hz.')
110
+ self.spectral=self.model.model._spectral(scalar,spec.frequency_hz,spec.block_size)
111
+ self.output_shapes=[(self.spectral.frequency.numel(),len(plan['weights']),6)
112
+ for _,_,plan,_ in self.model.model.plans]
113
+ self.layout_bytes=self.model.model.layout_reservation_bytes
114
+ points=sum(shape[1] for shape in self.output_shapes)
115
+ self.metadata_bytes=(4*points+len(self.output_shapes)*self.spectral.frequency.numel())*scalar.element_size()
116
+ else:
117
+ self.layout_bytes=0
118
+ monitors=[m for m in self.project.monitors if m.enabled]
119
+ if spec.frequency_hz is not None:
120
+ self.spectral=SpectralObservation(scalar,self.project.region,[m.component for m in monitors],
121
+ spec.frequency_hz,block_size=spec.block_size)
122
+ self.output_shapes=[(self.project.region.steps if self.spectral is None else self.spectral.frequency.numel(),len(monitors))]
123
+ self.metadata_bytes=0 if self.spectral is None else self.spectral.frequency.numel()*scalar.element_size()
124
+ self.output_dtype=(torch.complex128 if scalar.dtype==torch.float64 else torch.complex64) if self.spectral is not None or self.project.region.complex_fields else scalar.dtype
125
+ item=torch.empty((),dtype=self.output_dtype,device='cpu').element_size()
126
+ self.output_bytes=sum(math.prod(shape)*item for shape in self.output_shapes)
127
+
128
+ def check_fixed(self):
129
+ spec=self.spec
130
+ configuration=(spec.policy,spec.fixed_background_epsilon,
131
+ spec.parameter_indices,spec.block_size,spec.quadrature_counts)
132
+ if self.project.model_dump()!=self.snapshot or configuration!=self.configuration:
133
+ raise RuntimeError('Batch case configuration changed. Rebuild the batch.')
134
+ if getattr(spec.policy,'recorded',None) is not None:
135
+ self.model.check_fixed()
136
+
137
+ def inputs(self,parameters):
138
+ if max(self.spec.parameter_indices)>=len(parameters):raise ValueError('Case parameter index exceeds the supplied tensors.')
139
+ values=tuple(parameters[i] for i in self.spec.parameter_indices)
140
+ dtype=getattr(torch,self.project.region.precision)
141
+ if any(v.device.type!='cpu' or v.dtype!=dtype for v in values):
142
+ raise ValueError('Case materials must be CPU tensors matching project precision.')
143
+ return values
144
+
145
+ def reservation(self,parameters):
146
+ self.check_fixed()
147
+ values=self.inputs(parameters)
148
+ shapes=tuple(tuple(v.shape) for v in values)
149
+ frequency=None if self.spectral is None else self.spectral.frequency
150
+ policy=self.spec.policy
151
+ if getattr(policy,'recorded',None) is not None:
152
+ from .recorded_execution import _recorded_reservation
153
+ return _recorded_reservation(self.project,shapes,policy,frequency,
154
+ block_size=self.spec.block_size,quadrature_counts=self.spec.quadrature_counts,
155
+ plane=self.model.model,fixed_background_epsilon=self.spec.fixed_background_epsilon)
156
+ if policy.resident is not None:
157
+ return _resident_reservation(self.project,shapes,policy,frequency,block_size=self.spec.block_size,
158
+ quadrature_counts=self.spec.quadrature_counts,plane=self.model.model if self.planes else None)
159
+ options=_streamed_options(policy)
160
+ if self.planes:
161
+ return plane_reservation(self.model.model,shapes,options,frequency,block_size=self.spec.block_size)
162
+ poles=0
163
+ if self.dispersive:
164
+ from .adjoint_memory import _material_shapes
165
+ shapes,poles=_material_shapes(self.project.region,shapes)
166
+ elif shapes[0] not in (self.project.region.shape,self.project.region.shape+(3,)):
167
+ raise ValueError('epsilon shape must match the grid.')
168
+ return _reservation(self.project,values[0],options,self.spectral,pole_count=poles,
169
+ parameter_shapes=shapes if self.dispersive else None)
170
+
171
+ def evaluate(self,parameters):
172
+ self.check_fixed()
173
+ values=self.inputs(parameters)
174
+ if self.planes:return self.model(*values,frequency_hz=self.spectral.frequency,block_size=self.spec.block_size)
175
+ if self.spectral is not None:
176
+ return self.model.spectrum(*values,frequency_hz=self.spectral.frequency,block_size=self.spec.block_size)
177
+ return self.model(*values)
178
+
179
+
180
+ class _BatchRun:
181
+ def __init__(self,cases,options,parameters):
182
+ self.cases,self.options=cases,options
183
+ self.output_bytes=sum(case.output_bytes for case in cases)
184
+ if self.output_bytes>options.output_budget_bytes:raise ValueError('Batch output budget exceeded before simulation.')
185
+ parameters_bytes=sum(v.numel()*v.element_size() for v in parameters)
186
+ metadata=sum(case.layout_bytes+case.metadata_bytes+64*1024 for case in cases)
187
+ # All results + incoming seeds + a comparison/copy allowance coexist
188
+ # with an active case. Reserve accumulation and gradient-copy carriers.
189
+ self.overhead=3*self.output_bytes+4*parameters_bytes+metadata
190
+ self.templates=[]
191
+ self.report=dict(execution='sequential_case_replay',case_count=len(cases),
192
+ full_case_graph_retention=False,output_bytes=self.output_bytes,
193
+ parameter_bytes=parameters_bytes,metadata_reservation_bytes=metadata,
194
+ batch_overhead_bytes=self.overhead,forward_cases=0,replayed_cases=0,
195
+ scope='Shared solver/output/gradient-carrier admission. Caller geometry, input storage, objective and optimizer graphs, CUDA context and OS file cache excluded. Sequential, first-order execution.')
196
+ reservations=[self.admit(case,parameters) for case in cases]
197
+ self.report.update(case_reservations=reservations,
198
+ host_reservation_bytes=self.overhead+max(r['host_reservation_bytes'] for r in reservations),
199
+ gpu_reservation_bytes=max(r['gpu_reservation_bytes'] for r in reservations),
200
+ disk_reservation_bytes=max(r.get('disk_reservation_bytes',r.get('disk_checkpoint_reservation_bytes',0)) for r in reservations))
201
+
202
+ def admit(self,case,parameters):
203
+ reservation=case.reservation(parameters)
204
+ host=self.overhead+reservation['host_reservation_bytes']
205
+ available=host_memory()['available_bytes']
206
+ limit=self.options.host_budget_bytes
207
+ if available is not None:limit=min(limit,int(available*.8))
208
+ if host>limit:raise ValueError('Batch outputs, gradients and active solver exceed the shared host budget.')
209
+ if reservation['gpu_reservation_bytes']>self.options.gpu_budget_bytes:
210
+ raise ValueError('Active solver exceeds the shared batch GPU budget.')
211
+ disk=reservation.get('disk_reservation_bytes',reservation.get('disk_checkpoint_reservation_bytes',0))
212
+ if disk and (self.options.disk_budget_bytes is None or disk>self.options.disk_budget_bytes):
213
+ raise ValueError('File-backed cases require a sufficient shared batch disk budget.')
214
+ return reservation
215
+
216
+
217
+ def _replay(run,case,parameters,expected,seeds):
218
+ # Local scope must release the native system before another case starts.
219
+ run.admit(case,parameters)
220
+ with torch.enable_grad():
221
+ values=case.inputs(parameters)
222
+ inputs=tuple(p.detach().requires_grad_(p.requires_grad) for p in values)
223
+ # Evaluate the case's local material tuple without copying the whole
224
+ # global parameter set. Repeated index bindings remain independent leaves.
225
+ if case.planes:result=case.model(*inputs,frequency_hz=case.spectral.frequency,block_size=case.spec.block_size)
226
+ elif case.spectral is not None:result=case.model.spectrum(*inputs,frequency_hz=case.spectral.frequency,block_size=case.spec.block_size)
227
+ else:result=case.model(*inputs)
228
+ outputs=_tensors(result)
229
+ active_outputs=[];active_seeds=[]
230
+ for value,want,seed in zip(outputs,expected,seeds):
231
+ if value.shape!=want.shape or value.dtype!=want.dtype or not torch.allclose(value.detach(),want,
232
+ rtol=run.options.replay_rtol,atol=run.options.replay_atol):
233
+ raise RuntimeError('Recomputed FDTD case output drifted. Keep all case settings fixed.')
234
+ if seed is not None and value.requires_grad:
235
+ active_outputs.append(value);active_seeds.append(seed)
236
+ active_inputs=tuple(p for p in inputs if p.requires_grad)
237
+ gradients=torch.autograd.grad(active_outputs,active_inputs,active_seeds,allow_unused=True) if active_outputs and active_inputs else (None,)*len(active_inputs)
238
+ iterator=iter(gradients)
239
+ report=next(iter(result.values())).report if isinstance(result,dict) else result.report
240
+ return tuple(next(iterator) if p.requires_grad else None for p in inputs),dict(report)
241
+
242
+
243
+ class _Batch(torch.autograd.Function):
244
+ @staticmethod
245
+ def forward(ctx,run,*parameters):
246
+ started=time.perf_counter()
247
+ outputs=[]
248
+ for case in run.cases:
249
+ run.admit(case,parameters)
250
+ result=case.evaluate(parameters)
251
+ tensors=_tensors(result)
252
+ if len(tensors)!=len(case.output_shapes):raise RuntimeError('Unexpected case output count.')
253
+ for value,shape in zip(tensors,case.output_shapes):
254
+ if value.shape!=shape or value.dtype!=case.output_dtype or value.device.type!='cpu':
255
+ raise RuntimeError('Case output differs from its admitted layout.')
256
+ if not bool(torch.isfinite(value).all()):raise RuntimeError('Nonfinite FDTD case output.')
257
+ outputs.extend(tensors)
258
+ run.templates.append(_replace_tensors(result,[v.new_empty(0) for v in tensors]))
259
+ run.report['forward_cases']+=1
260
+ del result,tensors,value
261
+ ctx.run,ctx.output_count=run,len(outputs)
262
+ ctx.save_for_backward(*outputs,*parameters)
263
+ ctx.set_materialize_grads(False)
264
+ run.report['batch_forward_seconds']=time.perf_counter()-started
265
+ return tuple(outputs)
266
+
267
+ @staticmethod
268
+ def backward(ctx,*seeds):
269
+ if torch.is_grad_enabled():raise RuntimeError('Adjoint batches support first-order derivatives only.')
270
+ saved=ctx.saved_tensors
271
+ expected,parameters=saved[:ctx.output_count],saved[ctx.output_count:]
272
+ run=ctx.run
273
+ started=time.perf_counter()
274
+ accumulated=[None]*len(parameters)
275
+ offset=0
276
+ run.report['replayed_cases']=0
277
+ run.report['replay_reports']=[]
278
+ for case in run.cases:
279
+ count=len(case.output_shapes)
280
+ group=seeds[offset:offset+count]
281
+ if any(seed is not None for seed in group) and any(parameters[i].requires_grad for i in case.spec.parameter_indices):
282
+ gradients,report=_replay(run,case,parameters,expected[offset:offset+count],group)
283
+ for index,gradient in zip(case.spec.parameter_indices,gradients):
284
+ if gradient is not None:
285
+ if accumulated[index] is None:accumulated[index]=gradient.clone()
286
+ else:accumulated[index].add_(gradient)
287
+ del gradients
288
+ run.report['replayed_cases']+=1
289
+ run.report['replay_reports'].append(report)
290
+ offset+=count
291
+ run.report['batch_backward_seconds']=time.perf_counter()-started
292
+ return None,*accumulated
293
+
294
+
295
+ class RecomputedAdjointBatch(torch.nn.Module):
296
+ """Return ordinary FDTD result objects with one shared first-order backward.
297
+
298
+ All cases are admitted together before any simulation. Results remain on
299
+ CPU and can feed a nonseparable Torch objective. Backward replays one case
300
+ graph at a time and sums derivatives of shared material parameters. This
301
+ is sequential case replay, not concurrent CUDA execution or automatic
302
+ policy tuning. Select case policies with tune_adjoint_execution first.
303
+ """
304
+ def __init__(self,cases,options=None):
305
+ super().__init__()
306
+ specs=tuple(cases)
307
+ if not specs or not all(isinstance(case,AdjointCase) for case in specs):
308
+ raise ValueError('Provide at least one AdjointCase.')
309
+ self.options=options or AdjointBatchOptions()
310
+ if not isinstance(self.options,AdjointBatchOptions):raise ValueError('Use AdjointBatchOptions.')
311
+ from .boundaries import reject_pmc_faces
312
+ for case in specs:reject_pmc_faces(case.project.region,'RecomputedAdjointBatch')
313
+ self._cases=tuple(_PreparedCase(case) for case in specs)
314
+
315
+ def _plan(self,parameters):
316
+ if not parameters or not all(isinstance(p,torch.Tensor) and p.device.type=='cpu' and p.dtype in (torch.float32,torch.float64) for p in parameters):
317
+ raise ValueError('Pass explicit real CPU material tensors.')
318
+ return _BatchRun(self._cases,self.options,parameters)
319
+
320
+ def plan(self,*parameters):
321
+ """Check metadata and live shared budgets without allocating fields."""
322
+ return self._plan(parameters).report
323
+
324
+ def forward(self,*parameters):
325
+ if torch.is_inference_mode_enabled():raise ValueError('Use torch.no_grad(), not inference_mode, for adjoint batches.')
326
+ run=self._plan(parameters)
327
+ flat=_Batch.apply(run,*parameters)
328
+ results=[];offset=0
329
+ for case,template in zip(run.cases,run.templates):
330
+ count=len(case.output_shapes)
331
+ results.append(_replace_tensors(template,flat[offset:offset+count]))
332
+ offset+=count
333
+ return AdjointBatchResult(tuple(results),run.report)