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.
- torchfdtd/__init__.py +206 -0
- torchfdtd/adjoint_batch.py +333 -0
- torchfdtd/adjoint_memory.py +268 -0
- torchfdtd/adjoint_planes.py +280 -0
- torchfdtd/adjoint_spectrum.py +97 -0
- torchfdtd/angular_spectrum.py +516 -0
- torchfdtd/anisotropy.py +418 -0
- torchfdtd/anisotropy_dispersive.py +395 -0
- torchfdtd/batch.py +344 -0
- torchfdtd/boundaries.py +443 -0
- torchfdtd/capabilities.json +13330 -0
- torchfdtd/capabilities.py +882 -0
- torchfdtd/cli.py +160 -0
- torchfdtd/convergence.py +178 -0
- torchfdtd/cuda_adjoint.py +228 -0
- torchfdtd/cuda_batch.py +170 -0
- torchfdtd/cuda_bootstrap.py +57 -0
- torchfdtd/cuda_complex.py +101 -0
- torchfdtd/cuda_complex_adjoint.py +149 -0
- torchfdtd/cuda_diagnostics.py +135 -0
- torchfdtd/cuda_dispersive_adjoint.py +230 -0
- torchfdtd/cuda_graph.py +57 -0
- torchfdtd/cuda_kernels.py +348 -0
- torchfdtd/cuda_memory.py +78 -0
- torchfdtd/cuda_monitors.py +156 -0
- torchfdtd/cuda_subpixel.py +42 -0
- torchfdtd/cuda_tfsf.py +82 -0
- torchfdtd/density_layer.py +126 -0
- torchfdtd/design.py +123 -0
- torchfdtd/design_checkpoint.py +139 -0
- torchfdtd/design_parameterization.py +209 -0
- torchfdtd/design_problem.py +314 -0
- torchfdtd/design_service.py +106 -0
- torchfdtd/detector_allocation.py +52 -0
- torchfdtd/differentiable.py +875 -0
- torchfdtd/differentiable_geometry.py +338 -0
- torchfdtd/dispersive_adjoint.py +386 -0
- torchfdtd/doctor.py +191 -0
- torchfdtd/domain_decomposition.py +365 -0
- torchfdtd/electron_model.py +99 -0
- torchfdtd/endpoint_native.py +228 -0
- torchfdtd/endpoint_project.py +196 -0
- torchfdtd/execution_modes.py +722 -0
- torchfdtd/execution_tuning.py +305 -0
- torchfdtd/fabrication.py +245 -0
- torchfdtd/farfield_api.py +177 -0
- torchfdtd/feature_inventory.json +35036 -0
- torchfdtd/field_monitors.py +245 -0
- torchfdtd/fsp.py +348 -0
- torchfdtd/fsp_binary.py +427 -0
- torchfdtd/fsp_geometry.py +296 -0
- torchfdtd/fsp_instruments.py +139 -0
- torchfdtd/fsp_native.py +829 -0
- torchfdtd/fsp_objects.py +141 -0
- torchfdtd/fsp_service.py +183 -0
- torchfdtd/fsp_settings.py +396 -0
- torchfdtd/gds.py +522 -0
- torchfdtd/gds_ports.py +166 -0
- torchfdtd/gds_service.py +112 -0
- torchfdtd/geometry.py +148 -0
- torchfdtd/grouped_batch.py +146 -0
- torchfdtd/identity.py +102 -0
- torchfdtd/information.py +98 -0
- torchfdtd/injection.py +141 -0
- torchfdtd/material_fit.py +534 -0
- torchfdtd/materials.py +193 -0
- torchfdtd/memory_accounting.py +378 -0
- torchfdtd/memory_profile.py +114 -0
- torchfdtd/mesh.py +134 -0
- torchfdtd/mode_branches.py +592 -0
- torchfdtd/mode_injection.py +386 -0
- torchfdtd/mode_network.py +359 -0
- torchfdtd/mode_network_project.py +322 -0
- torchfdtd/mode_network_service.py +246 -0
- torchfdtd/mode_network_worker.py +56 -0
- torchfdtd/mode_ports.py +361 -0
- torchfdtd/models.py +969 -0
- torchfdtd/open_mode_injection.py +171 -0
- torchfdtd/open_mode_operators.py +138 -0
- torchfdtd/open_mode_ports.py +350 -0
- torchfdtd/optical_data.py +91 -0
- torchfdtd/oracle_memory.py +136 -0
- torchfdtd/periodic_adjoint.py +335 -0
- torchfdtd/periodic_design.py +204 -0
- torchfdtd/periodic_response.py +85 -0
- torchfdtd/plan.py +544 -0
- torchfdtd/plane_execution.py +117 -0
- torchfdtd/pmc_cpml.py +286 -0
- torchfdtd/pmc_cpml_cuda.py +121 -0
- torchfdtd/pmc_cuda.py +203 -0
- torchfdtd/pmc_reference.py +174 -0
- torchfdtd/pmc_simulation.py +269 -0
- torchfdtd/polarization.py +82 -0
- torchfdtd/ports.py +316 -0
- torchfdtd/priorities.py +96 -0
- torchfdtd/propagation_api.py +206 -0
- torchfdtd/pupil_response.py +36 -0
- torchfdtd/radiation.py +668 -0
- torchfdtd/radiation_api.py +75 -0
- torchfdtd/radiation_box.py +363 -0
- torchfdtd/radiation_box_io.py +242 -0
- torchfdtd/radiation_io.py +68 -0
- torchfdtd/recomputed_batch.py +90 -0
- torchfdtd/recorded_execution.py +167 -0
- torchfdtd/recorded_observations.py +71 -0
- torchfdtd/reference_cache.py +58 -0
- torchfdtd/reservation_registry.py +99 -0
- torchfdtd/response_cache.py +128 -0
- torchfdtd/result_store.py +263 -0
- torchfdtd/results.py +481 -0
- torchfdtd/reversible.py +278 -0
- torchfdtd/reversible_cpml.py +603 -0
- torchfdtd/reversible_cpml_kernels.py +355 -0
- torchfdtd/reversible_cpml_memory.py +255 -0
- torchfdtd/reversible_cpml_planes.py +82 -0
- torchfdtd/reversible_cuda_fused.py +434 -0
- torchfdtd/reversible_cuda_tuning.py +241 -0
- torchfdtd/reversible_lorentz.py +641 -0
- torchfdtd/reversible_lorentz_cuda.py +284 -0
- torchfdtd/reversible_memory.py +99 -0
- torchfdtd/reversible_trace.py +273 -0
- torchfdtd/run_control.py +159 -0
- torchfdtd/server.py +529 -0
- torchfdtd/session.py +488 -0
- torchfdtd/solver.py +906 -0
- torchfdtd/source_adjoint.py +232 -0
- torchfdtd/source_adjoint_cuda.py +73 -0
- torchfdtd/source_parameters.py +65 -0
- torchfdtd/source_preview.py +229 -0
- torchfdtd/spacetime.py +540 -0
- torchfdtd/spectra.py +116 -0
- torchfdtd/stability_checks.py +100 -0
- torchfdtd/staging.py +152 -0
- torchfdtd/state_store.py +199 -0
- torchfdtd/streamed.py +612 -0
- torchfdtd/streamed_cost.py +46 -0
- torchfdtd/streamed_density.py +245 -0
- torchfdtd/streamed_dispersive.py +244 -0
- torchfdtd/streamed_geometry.py +345 -0
- torchfdtd/streamed_planning.py +118 -0
- torchfdtd/streamed_restart.py +703 -0
- torchfdtd/streamed_tensor.py +181 -0
- torchfdtd/streamed_tuning.py +404 -0
- torchfdtd/streamed_work.py +228 -0
- torchfdtd/subpixel.py +206 -0
- torchfdtd/subpixel_dispersive.py +426 -0
- torchfdtd/subpixel_geometry.py +228 -0
- torchfdtd/tensor_batch.py +336 -0
- torchfdtd/tensor_native.py +137 -0
- torchfdtd/tensor_packet.py +86 -0
- torchfdtd/tensor_project.py +147 -0
- torchfdtd/tfsf.py +228 -0
- torchfdtd/tile_workspace.py +173 -0
- torchfdtd/tiled.py +605 -0
- torchfdtd/tuning.py +160 -0
- torchfdtd/waveforms.py +74 -0
- torchfdtd/web/assets/index-BQKZ9PI6.js +4151 -0
- torchfdtd/web/assets/index-DErGp_7h.css +1 -0
- torchfdtd/web/index.html +5 -0
- torchfdtd-1.1.7.dist-info/METADATA +289 -0
- torchfdtd-1.1.7.dist-info/RECORD +166 -0
- torchfdtd-1.1.7.dist-info/WHEEL +5 -0
- torchfdtd-1.1.7.dist-info/entry_points.txt +2 -0
- torchfdtd-1.1.7.dist-info/licenses/LICENSE +22 -0
- torchfdtd-1.1.7.dist-info/licenses/THIRD_PARTY_NOTICES.txt +132 -0
- 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)
|