diffinytrace 2.1__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.
- diffinytrace/__init__.py +122 -0
- diffinytrace/basis_functions/__init__.py +14 -0
- diffinytrace/basis_functions/bspline.py +521 -0
- diffinytrace/basis_functions/chebyshev.py +3 -0
- diffinytrace/basis_functions/legendre.py +77 -0
- diffinytrace/basis_functions/zernike.py +235 -0
- diffinytrace/config.py +140 -0
- diffinytrace/constraints.py +54 -0
- diffinytrace/element.py +1660 -0
- diffinytrace/export/__init__.py +8 -0
- diffinytrace/export/cad.py +253 -0
- diffinytrace/gaussian_smoother.py +530 -0
- diffinytrace/hat_smoother.py +44 -0
- diffinytrace/integrators.py +452 -0
- diffinytrace/intersection.py +285 -0
- diffinytrace/optimize.py +808 -0
- diffinytrace/physical_object.py +150 -0
- diffinytrace/plotting/__init__.py +16 -0
- diffinytrace/plotting/core.py +92 -0
- diffinytrace/plotting/quantity2D.py +188 -0
- diffinytrace/plotting/system2D.py +220 -0
- diffinytrace/plotting/system3D.py +327 -0
- diffinytrace/plotting/wavelength.py +231 -0
- diffinytrace/refractive_index.py +101 -0
- diffinytrace/render.py +77 -0
- diffinytrace/source.py +661 -0
- diffinytrace/spectrum.py +79 -0
- diffinytrace/surface.py +468 -0
- diffinytrace/target_grid.py +399 -0
- diffinytrace/transforms.py +472 -0
- diffinytrace/utils/__init__.py +7 -0
- diffinytrace/utils/autograd.py +116 -0
- diffinytrace/utils/irradiance_importer.py +134 -0
- diffinytrace-2.1.dist-info/METADATA +26 -0
- diffinytrace-2.1.dist-info/RECORD +38 -0
- diffinytrace-2.1.dist-info/WHEEL +5 -0
- diffinytrace-2.1.dist-info/licenses/LICENSE +21 -0
- diffinytrace-2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# Copyright (c) 2025 Martin Pflaum
|
|
2
|
+
# This file is part of the diffinytrace project, licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"gaussian_func_1D",
|
|
7
|
+
"gaussian_func_2D",
|
|
8
|
+
"calc_smooth_desired_irradiance",
|
|
9
|
+
"GaussianSmoother",
|
|
10
|
+
"make_evaluation_function",
|
|
11
|
+
"make_merit_function",
|
|
12
|
+
"GaussianSmootherSquare"
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
import torch
|
|
16
|
+
import math
|
|
17
|
+
import numpy as np
|
|
18
|
+
from .integrators import Cube
|
|
19
|
+
import torch
|
|
20
|
+
from typing import Callable,List,Tuple,Optional
|
|
21
|
+
import gc
|
|
22
|
+
import torch
|
|
23
|
+
import numpy as np
|
|
24
|
+
from .element import trace_to_detector,SequentialOpticalSystem
|
|
25
|
+
import math
|
|
26
|
+
from .source import LightSource
|
|
27
|
+
from .target_grid import Grid
|
|
28
|
+
import gc
|
|
29
|
+
import warnings
|
|
30
|
+
from .render import binned_irradiance
|
|
31
|
+
from torchmetrics.image import StructuralSimilarityIndexMeasure
|
|
32
|
+
#pip install torchmetrics
|
|
33
|
+
|
|
34
|
+
def gaussian_func_1D(eval_points:torch.Tensor,
|
|
35
|
+
x_range,
|
|
36
|
+
num_gauss_points:int,
|
|
37
|
+
sigma:float,
|
|
38
|
+
include_boundary=False)->torch.Tensor:
|
|
39
|
+
"""
|
|
40
|
+
Gaussian function for 1D convolution.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
eval_points (torch.Tensor): Points where the Gaussian function is evaluated.
|
|
44
|
+
x_range (tuple): Range of the target plane.
|
|
45
|
+
num_gauss_points (int): Number of Gaussian points.
|
|
46
|
+
sigma (float): Standard deviation of the Gaussian function.
|
|
47
|
+
include_boundary (bool): Whether to include the boundary points.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
torch.Tensor: Evaluated Gaussian function.
|
|
51
|
+
"""
|
|
52
|
+
device = eval_points.device
|
|
53
|
+
dtype = eval_points.dtype
|
|
54
|
+
|
|
55
|
+
eval_points = eval_points.reshape(-1)
|
|
56
|
+
xgrid = None
|
|
57
|
+
if include_boundary:
|
|
58
|
+
xgrid = torch.linspace(x_range[0],x_range[1],num_gauss_points,dtype=dtype,device=device)
|
|
59
|
+
else:
|
|
60
|
+
xgrid = torch.linspace(x_range[0],x_range[1],num_gauss_points+1,dtype=dtype,device=device)
|
|
61
|
+
|
|
62
|
+
dxgrid = xgrid[1]-xgrid[0]
|
|
63
|
+
xgrid = xgrid[:-1]
|
|
64
|
+
xgrid = xgrid + dxgrid*0.5
|
|
65
|
+
dist = (xgrid.reshape(-1,1)-eval_points.reshape(1,-1))
|
|
66
|
+
|
|
67
|
+
const = 1.0/math.sqrt((2.0*math.pi)*sigma*sigma)
|
|
68
|
+
multiplier = const
|
|
69
|
+
out = multiplier*torch.exp(-(dist**2.0/(2.0*(sigma**2.0))))
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
def gaussian_func_2D(eval_points:torch.Tensor,
|
|
73
|
+
x_range,
|
|
74
|
+
y_range,
|
|
75
|
+
x_grid_size:int,
|
|
76
|
+
y_grid_size:int,
|
|
77
|
+
sigma:float|torch.Tensor,
|
|
78
|
+
val_multi:torch.Tensor|None=None,
|
|
79
|
+
summed:bool=True,
|
|
80
|
+
include_boundary=False)->torch.Tensor:
|
|
81
|
+
"""
|
|
82
|
+
Gaussian function for 2D convolution.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
eval_points (torch.Tensor): Points where the Gaussian function is evaluated.
|
|
86
|
+
y_range (tuple): Range of the target plane in the vertical direction.
|
|
87
|
+
x_range (tuple): Range of the target plane in the horizontal direction.
|
|
88
|
+
y_grid_size (int): Number of Gaussian points in the vertical direction.
|
|
89
|
+
x_grid_size (int): Number of Gaussian points in the horizontal direction.
|
|
90
|
+
sigma (float): Standard deviation of the Gaussian function.
|
|
91
|
+
val_multi (torch.Tensor|None): Optional multiplier for the Gaussian function.
|
|
92
|
+
summed (bool): Whether to sum the Gaussian function.
|
|
93
|
+
include_boundary (bool): Whether to include the boundary points.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
torch.Tensor: Evaluated Gaussian function.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
if eval_points.shape[-1] != 2:
|
|
100
|
+
raise RuntimeError("points need to be in local coordinates and shape [numraysx2]")
|
|
101
|
+
eval_points1 = eval_points[:,1]
|
|
102
|
+
eval_points2 = eval_points[:,0]
|
|
103
|
+
|
|
104
|
+
out1 = gaussian_func_1D(eval_points1,y_range,y_grid_size,sigma,include_boundary)
|
|
105
|
+
out2 = gaussian_func_1D(eval_points2,x_range,x_grid_size,sigma,include_boundary)
|
|
106
|
+
|
|
107
|
+
if not val_multi is None:
|
|
108
|
+
out1 = out1*val_multi.reshape(1,-1)
|
|
109
|
+
if summed is False:
|
|
110
|
+
|
|
111
|
+
print("summed=False should only be used when debugging!! Its very very slow.")
|
|
112
|
+
out = out1.reshape(y_grid_size,1,-1)*out2.reshape(1,x_grid_size,-1)
|
|
113
|
+
return out
|
|
114
|
+
return out1@out2.T
|
|
115
|
+
|
|
116
|
+
def calc_smooth_desired_irradiance(desired_irradiance_fun:Callable,
|
|
117
|
+
x_range:List[float],
|
|
118
|
+
y_range:List[float],
|
|
119
|
+
x_grid_size:int,
|
|
120
|
+
y_grid_size:int,
|
|
121
|
+
sigma:float,
|
|
122
|
+
num_integration_points:int,
|
|
123
|
+
num_splits=5,
|
|
124
|
+
dtype=torch.get_default_dtype(),
|
|
125
|
+
device=torch.get_default_device())->torch.Tensor:
|
|
126
|
+
"""
|
|
127
|
+
Calculates the smoothed desired irradiance using Gaussian convolution.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
desired_irradiance_fun (Callable): Function that computes the desired irradiance at given points.
|
|
131
|
+
x_range (List[float]): Range of the target plane in the x direction [min, max].
|
|
132
|
+
y_range (List[float]): Range of the target plane in the y direction [min, max].
|
|
133
|
+
x_grid_size (int): Number of pixels in the x direction.
|
|
134
|
+
y_grid_size (int): Number of pixels in the y direction.
|
|
135
|
+
sigma (float): Standard deviation of the Gaussian kernel.
|
|
136
|
+
num_integration_points (int): Number of integration points for numerical integration.
|
|
137
|
+
num_splits (int, optional): Number of splits for integration to reduce memory usage. Defaults to 5.
|
|
138
|
+
dtype (torch.dtype, optional): Data type for tensors. Defaults to torch.get_default_dtype().
|
|
139
|
+
device (torch.device, optional): Device for computation. Defaults to torch.get_default_device().
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
torch.Tensor: Smoothed desired irradiance map.
|
|
143
|
+
"""
|
|
144
|
+
gc.collect()
|
|
145
|
+
integrator = Cube([x_range,y_range])
|
|
146
|
+
points,weights = integrator.sample(num_integration_points,"sobol_pow2")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
splitted_points = torch.split(points, num_integration_points // num_splits)
|
|
151
|
+
splitted_weights = torch.split(weights, num_integration_points // num_splits)
|
|
152
|
+
|
|
153
|
+
with torch.no_grad():
|
|
154
|
+
out = []
|
|
155
|
+
for k in range(num_splits):
|
|
156
|
+
split_points = splitted_points[k].to(device=device,dtype=dtype)
|
|
157
|
+
split_weights = splitted_weights[k].to(device=device,dtype=dtype)
|
|
158
|
+
tmp = gaussian_func_2D(split_points,x_range,y_range,x_grid_size,y_grid_size,sigma=sigma,val_multi=desired_irradiance_fun(split_points)*split_weights)
|
|
159
|
+
#print("tmp.shape",tmp.shape)
|
|
160
|
+
out.append(tmp.detach())
|
|
161
|
+
#del split_points, split_weights
|
|
162
|
+
gc.collect()
|
|
163
|
+
|
|
164
|
+
out = torch.sum(torch.stack(out), dim=0)
|
|
165
|
+
del points, weights, splitted_points, splitted_weights
|
|
166
|
+
gc.collect()
|
|
167
|
+
#print("smoothed desired sum",out.sum())
|
|
168
|
+
return out
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class GaussianSmoother():
|
|
173
|
+
r"""
|
|
174
|
+
The GaussianSmoother class implements gaussian measurement functions but also computes smoothed desired irradiance distributions. For more information on this class please refer to the examples.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
x_range (list): Range of the target plane in the x direction [min, max].
|
|
178
|
+
y_range (list): Range of the target plane in the y direction [min, max].
|
|
179
|
+
x_grid_size (int): Number of pixels in the x direction.
|
|
180
|
+
y_grid_size (int): Number of pixels in the y direction.
|
|
181
|
+
sigma (float): Standard deviation of the Gaussian kernel.
|
|
182
|
+
desired_irradiance_fun (Callable): Function that computes the desired irradiance at given points.
|
|
183
|
+
smoothed_num_integration_points (int): Number of integration points for smoothing.
|
|
184
|
+
smoothed_num_splits (int): Number of splits for integration to reduce memory usage.
|
|
185
|
+
dtype (torch.dtype, optional): Data type for tensors. Defaults to torch.get_default_dtype().
|
|
186
|
+
device (torch.device, optional): Device for computation. Defaults to torch.get_default_device().
|
|
187
|
+
|
|
188
|
+
Attributes:
|
|
189
|
+
x_grid_size (int): Number of pixels in the x direction.
|
|
190
|
+
y_grid_size (int): Number of pixels in the y direction.
|
|
191
|
+
sigma (float): Standard deviation of the Gaussian kernel.
|
|
192
|
+
include_boundary (bool): Whether to include boundary points in the grid.
|
|
193
|
+
x_range (list): Range of the target plane in the x direction.
|
|
194
|
+
y_range (list): Range of the target plane in the y direction.
|
|
195
|
+
grid (Grid): Grid object for pixel centers.
|
|
196
|
+
discrete_desired_irradiance (torch.Tensor): Desired irradiance at pixel centers.
|
|
197
|
+
smoothed_desired_irradiance (torch.Tensor): Smoothed desired irradiance map.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def __init__(self,
|
|
201
|
+
x_range:list,
|
|
202
|
+
y_range:list,
|
|
203
|
+
x_grid_size:int,
|
|
204
|
+
y_grid_size:int,
|
|
205
|
+
sigma:float,
|
|
206
|
+
desired_irradiance_fun:Callable,
|
|
207
|
+
smoothed_num_integration_points:int,
|
|
208
|
+
smoothed_num_splits:int,
|
|
209
|
+
dtype=torch.get_default_dtype(),
|
|
210
|
+
device=torch.get_default_device()):
|
|
211
|
+
|
|
212
|
+
self.x_grid_size,self.y_grid_size = x_grid_size,y_grid_size
|
|
213
|
+
self.sigma = sigma
|
|
214
|
+
self.include_boundary = False
|
|
215
|
+
self.x_range,self.y_range = x_range,y_range
|
|
216
|
+
|
|
217
|
+
self.grid = Grid(x_range,y_range,x_grid_size,y_grid_size)
|
|
218
|
+
centers = self.grid.get_pixel_centers().reshape(-1,2)
|
|
219
|
+
self.discrete_desired_irradiance:torch.Tensor = desired_irradiance_fun(centers).reshape(y_grid_size,x_grid_size)
|
|
220
|
+
integrated_desired_irradiance = self.integrate_values(self.discrete_desired_irradiance)
|
|
221
|
+
self.discrete_desired_irradiance = self.discrete_desired_irradiance / integrated_desired_irradiance
|
|
222
|
+
new_desired_irradiance_fun = lambda points: desired_irradiance_fun(points) / integrated_desired_irradiance
|
|
223
|
+
|
|
224
|
+
self.new_desired_irradiance_fun = new_desired_irradiance_fun
|
|
225
|
+
self.smoothed_num_integration_points = smoothed_num_integration_points
|
|
226
|
+
self.smoothed_num_splits = smoothed_num_splits
|
|
227
|
+
self.dtype = dtype
|
|
228
|
+
self.device = device
|
|
229
|
+
|
|
230
|
+
self.smoothed_desired_irradiance:torch.Tensor = calc_smooth_desired_irradiance(new_desired_irradiance_fun,
|
|
231
|
+
x_range,y_range,
|
|
232
|
+
x_grid_size,
|
|
233
|
+
y_grid_size,
|
|
234
|
+
sigma=sigma,
|
|
235
|
+
num_integration_points=smoothed_num_integration_points,
|
|
236
|
+
num_splits=smoothed_num_splits,
|
|
237
|
+
dtype=dtype,
|
|
238
|
+
device=device)
|
|
239
|
+
|
|
240
|
+
def smoothed_irradiance(self,points:torch.Tensor,ray_multi:torch.Tensor,x_range=None,y_range=None)->torch.Tensor:
|
|
241
|
+
"""
|
|
242
|
+
Computes the smoothed irradiance at given points using a Gaussian kernel.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
points (torch.Tensor): Array of points where the irradiance is evaluated, shape [N, 2].
|
|
246
|
+
ray_multi (torch.Tensor): Multiplicative weights for each point, e.g., ray flux.
|
|
247
|
+
x_range (tuple, optional): Range of the target plane in the x direction. Defaults to None.
|
|
248
|
+
y_range (tuple, optional): Range of the target plane in the y direction. Defaults to None.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
torch.Tensor: Smoothed irradiance values at the specified points.
|
|
252
|
+
"""
|
|
253
|
+
if x_range is None:
|
|
254
|
+
x_range = self.x_range
|
|
255
|
+
if y_range is None:
|
|
256
|
+
y_range = self.y_range
|
|
257
|
+
return gaussian_func_2D(points,x_range,y_range,self.x_grid_size,self.y_grid_size,self.sigma,ray_multi,include_boundary=self.include_boundary)
|
|
258
|
+
|
|
259
|
+
def none_smoothed_irradiance(self,points:torch.Tensor,ray_multi:torch.Tensor)->torch.Tensor:
|
|
260
|
+
"""
|
|
261
|
+
Computes the non-smoothed irradiance at given points by summing ray contributions in each grid cell.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
points (torch.Tensor): Array of points where the irradiance is evaluated, shape [N, 2].
|
|
265
|
+
ray_multi (torch.Tensor): Multiplicative weights for each point, e.g., ray flux.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
torch.Tensor: Non-smoothed irradiance values at the specified grid cells.
|
|
269
|
+
"""
|
|
270
|
+
irradiance = self.grid.sum(points,ray_multi)/self.grid.get_pixel_area()
|
|
271
|
+
return irradiance
|
|
272
|
+
|
|
273
|
+
def integrate_values(self, vals:torch.Tensor,x_range=None,y_range=None)->torch.Tensor:
|
|
274
|
+
"""
|
|
275
|
+
Integrates the provided values over the grid using midpoint rule.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
vals (torch.Tensor): Values to integrate, typically irradiance or residuals, shape matching the grid.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
torch.Tensor: The integrated sum over the grid.
|
|
282
|
+
"""
|
|
283
|
+
if x_range is None:
|
|
284
|
+
x_range = self.x_range
|
|
285
|
+
if y_range is None:
|
|
286
|
+
y_range = self.y_range
|
|
287
|
+
integrator = Cube([x_range, y_range])
|
|
288
|
+
_, weights = integrator.sample([self.x_grid_size, self.y_grid_size], "midpoint")
|
|
289
|
+
weights = weights.to(device=vals.device, dtype=vals.dtype)
|
|
290
|
+
vals = vals.reshape(-1)
|
|
291
|
+
return (vals * weights).sum()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class GaussianSmootherSquare(GaussianSmoother):
|
|
295
|
+
r"""
|
|
296
|
+
This class is a specialized version of GaussianSmoother for cases where the x and y ranges are identical,
|
|
297
|
+
and the grid is square (same number of pixels in both directions).
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
x_range (list): Range of the target plane in both x and y directions [min, max].
|
|
301
|
+
x_grid_size (int): Number of pixels in both x and y directions.
|
|
302
|
+
sigma (float): Standard deviation of the Gaussian kernel.
|
|
303
|
+
desired_irradiance_fun (Callable): Function that computes the desired irradiance at given points.
|
|
304
|
+
smoothed_num_integration_points (int): Number of integration points for smoothing.
|
|
305
|
+
smoothed_num_splits (int): Number of splits for integration to reduce memory usage.
|
|
306
|
+
dtype (torch.dtype, optional): Data type for tensors. Defaults to torch.get_default_dtype().
|
|
307
|
+
device (torch.device, optional): Device for computation. Defaults to torch.get_default_device().
|
|
308
|
+
"""
|
|
309
|
+
def __init__(self,
|
|
310
|
+
aperture_radius:list,
|
|
311
|
+
grid_size:int,
|
|
312
|
+
sigma:float,
|
|
313
|
+
desired_irradiance_fun:Callable,
|
|
314
|
+
smoothed_num_integration_points:int,
|
|
315
|
+
smoothed_num_splits:int,
|
|
316
|
+
dtype=torch.get_default_dtype(),
|
|
317
|
+
device=torch.get_default_device()):
|
|
318
|
+
|
|
319
|
+
super().__init__(x_range=[-aperture_radius,aperture_radius],y_range=[-aperture_radius,aperture_radius],
|
|
320
|
+
x_grid_size=grid_size,
|
|
321
|
+
y_grid_size=grid_size,
|
|
322
|
+
sigma=sigma,
|
|
323
|
+
desired_irradiance_fun=desired_irradiance_fun,
|
|
324
|
+
smoothed_num_integration_points=smoothed_num_integration_points,
|
|
325
|
+
smoothed_num_splits=smoothed_num_splits,
|
|
326
|
+
dtype=dtype,
|
|
327
|
+
device=device)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def make_evaluation_function(optical_system:SequentialOpticalSystem,
|
|
331
|
+
sequence:List,
|
|
332
|
+
source:LightSource,
|
|
333
|
+
detector,
|
|
334
|
+
smoother:GaussianSmoother,
|
|
335
|
+
num_splits:int=10,
|
|
336
|
+
num_rays_per_split:int=100000,
|
|
337
|
+
method_ray_tracing="monte_carlo",
|
|
338
|
+
device=torch.get_default_device())->Callable:
|
|
339
|
+
"""
|
|
340
|
+
Creates an evaluation function for comparing simulated and desired irradiance.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
optical_system (SequentialOpticalSystem): The optical system to be used for ray tracing.
|
|
344
|
+
sequence: The sequence of optical elements.
|
|
345
|
+
source (LightSource): The light source for the simulation.
|
|
346
|
+
detector: The detector object.
|
|
347
|
+
smoother (GaussianSmoother): Smoother object for irradiance comparison.
|
|
348
|
+
num_splits (int, optional): Number of splits for ray tracing to reduce memory usage. Defaults to 10.
|
|
349
|
+
num_rays_per_split (int, optional): Number of rays per split. Defaults to 1,000,000.
|
|
350
|
+
method_ray_tracing (str, optional): Ray tracing method ('monte_carlo', etc.). Defaults to "monte_carlo".
|
|
351
|
+
device (torch.device, optional): Device for computation. Defaults to torch.get_default_device().
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
Callable: A function that computes the L2 error between simulated and desired irradiance.
|
|
355
|
+
"""
|
|
356
|
+
smoother.x_range
|
|
357
|
+
L = smoother.x_range[1]-smoother.x_range[0]
|
|
358
|
+
maxirr_est = (1/(L**2))*10
|
|
359
|
+
ssim = StructuralSimilarityIndexMeasure(data_range=maxirr_est)
|
|
360
|
+
|
|
361
|
+
def evaluate():
|
|
362
|
+
raycounting_list = []
|
|
363
|
+
for k in (range(num_splits)):
|
|
364
|
+
tmp = binned_irradiance(optical_system=optical_system,sequence=sequence,source=source,detector=detector,grid=smoother.grid,num_rays=num_rays_per_split,method_ray_tracing=method_ray_tracing,device=device)
|
|
365
|
+
tmp = tmp.detach().cpu()
|
|
366
|
+
raycounting_list.append(tmp)
|
|
367
|
+
raycounting = torch.mean(torch.stack(raycounting_list),dim=0).detach().cpu()
|
|
368
|
+
|
|
369
|
+
smoother.last_raycounting = raycounting.detach().cpu()
|
|
370
|
+
residual = raycounting.cpu().reshape(-1)-smoother.discrete_desired_irradiance.cpu().reshape(-1)
|
|
371
|
+
rmse = torch.sqrt(torch.mean((raycounting.cpu().reshape(-1)-smoother.discrete_desired_irradiance.cpu().reshape(-1))**2.0))
|
|
372
|
+
ssim_error = ssim(raycounting.cpu().reshape(1,1,smoother.x_grid_size,smoother.x_grid_size),smoother.discrete_desired_irradiance.cpu().reshape(1,1,smoother.x_grid_size,smoother.x_grid_size))
|
|
373
|
+
|
|
374
|
+
L2_error = torch.sqrt(smoother.integrate_values(residual**2))
|
|
375
|
+
#RMSE = torch.sum((residual**2))
|
|
376
|
+
return L2_error,rmse,ssim_error
|
|
377
|
+
return evaluate
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
def make_evaluation_function_rmse(optical_system:SequentialOpticalSystem,
|
|
381
|
+
sequence:List,
|
|
382
|
+
source:LightSource,
|
|
383
|
+
detector,
|
|
384
|
+
smoother:GaussianSmoother,
|
|
385
|
+
num_splits:int=10,
|
|
386
|
+
num_rays_per_split:int=100000,
|
|
387
|
+
method_ray_tracing="monte_carlo",
|
|
388
|
+
device=torch.get_default_device())->Callable:
|
|
389
|
+
def evaluate():
|
|
390
|
+
raycounting_list = []
|
|
391
|
+
for k in (range(num_splits)):
|
|
392
|
+
tmp = binned_irradiance(optical_system=optical_system,sequence=sequence,source=source,detector=detector,grid=smoother.grid,num_rays=num_rays_per_split,method_ray_tracing=method_ray_tracing,device=device)
|
|
393
|
+
tmp = tmp.detach().cpu()
|
|
394
|
+
raycounting_list.append(tmp)
|
|
395
|
+
raycounting = torch.mean(torch.stack(raycounting_list),dim=0).detach().cpu()
|
|
396
|
+
|
|
397
|
+
smoother.last_raycounting = raycounting.detach().cpu()
|
|
398
|
+
#residual = raycounting.cpu().reshape(-1)-smoother.discrete_desired_irradiance.cpu().reshape(-1)
|
|
399
|
+
L2_error = torch.sqrt(torch.mean((raycounting.cpu().reshape(-1)-smoother.discrete_desired_irradiance.cpu().reshape(-1))**2.0))
|
|
400
|
+
#L2_error = torch.sqrt(smoother.integrate_values(residual**2))
|
|
401
|
+
#RMSE = torch.sum((residual**2))
|
|
402
|
+
return L2_error
|
|
403
|
+
return evaluate
|
|
404
|
+
|
|
405
|
+
def make_evaluation_function_ssim(optical_system:SequentialOpticalSystem,
|
|
406
|
+
sequence:List,
|
|
407
|
+
source:LightSource,
|
|
408
|
+
detector,
|
|
409
|
+
smoother:GaussianSmoother,
|
|
410
|
+
num_splits:int=10,
|
|
411
|
+
num_rays_per_split:int=100000,
|
|
412
|
+
method_ray_tracing="monte_carlo",
|
|
413
|
+
device=torch.get_default_device())->Callable:
|
|
414
|
+
smoother.x_range
|
|
415
|
+
L = smoother.x_range[1]-smoother.x_range[0]
|
|
416
|
+
maxirr_est = (1/(L**2))*10
|
|
417
|
+
ssim = StructuralSimilarityIndexMeasure(data_range=maxirr_est)
|
|
418
|
+
def evaluate():
|
|
419
|
+
raycounting_list = []
|
|
420
|
+
for k in (range(num_splits)):
|
|
421
|
+
tmp = binned_irradiance(optical_system=optical_system,sequence=sequence,source=source,detector=detector,grid=smoother.grid,num_rays=num_rays_per_split,method_ray_tracing=method_ray_tracing,device=device)
|
|
422
|
+
tmp = tmp.detach().cpu()
|
|
423
|
+
raycounting_list.append(tmp)
|
|
424
|
+
raycounting = torch.mean(torch.stack(raycounting_list),dim=0).detach().cpu()
|
|
425
|
+
|
|
426
|
+
smoother.last_raycounting = raycounting.detach().cpu()
|
|
427
|
+
#residual = raycounting.cpu().reshape(-1)-smoother.discrete_desired_irradiance.cpu().reshape(-1)
|
|
428
|
+
L2_error = ssim(raycounting.cpu().reshape(-1),smoother.discrete_desired_irradiance.cpu().reshape(-1))
|
|
429
|
+
#L2_error = torch.sqrt(smoother.integrate_values(residual**2))
|
|
430
|
+
#RMSE = torch.sum((residual**2))
|
|
431
|
+
return L2_error
|
|
432
|
+
return evaluate
|
|
433
|
+
"""
|
|
434
|
+
|
|
435
|
+
def make_merit_function(optical_system:SequentialOpticalSystem,
|
|
436
|
+
sequence:List,
|
|
437
|
+
source:LightSource,
|
|
438
|
+
detector,
|
|
439
|
+
smoother:GaussianSmoother,
|
|
440
|
+
num_rays:int,
|
|
441
|
+
method_ray_tracing="sobol_pow2",
|
|
442
|
+
use_desired_irradiance_smoothing=True,
|
|
443
|
+
device=torch.get_default_device(),
|
|
444
|
+
T_margin=None)->Callable:
|
|
445
|
+
"""
|
|
446
|
+
Creates a merit function to obtain a desired irradiance distribution for the given optical system, source, and detector.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
optical_system (SequentialOpticalSystem): The optical system to be used.
|
|
450
|
+
sequence: The sequence of elements in the optical system.
|
|
451
|
+
source (LightSource): The light source to be used.
|
|
452
|
+
detector: The detector to be used.
|
|
453
|
+
num_rays (int): Number of rays to be traced.
|
|
454
|
+
smoother (Smoother): The smoother object for merit function calculation.
|
|
455
|
+
device: The device to be used for calculations.
|
|
456
|
+
method_ray_tracing (str): Method for ray tracing ('sobol' or 'midpoint').
|
|
457
|
+
use_desired_irradiance_smoothing (bool): Whether to use desired irradiance smoothing.
|
|
458
|
+
use_power_correction (bool): Whether to use power correction.
|
|
459
|
+
save_last_eval (bool): Whether to save the last evaluation.
|
|
460
|
+
T_margin (float|None): Optional margin for integration domain if it is None the integration domain will not be adjusted on the fly.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
Callable: A function that computes the merit value.
|
|
464
|
+
"""
|
|
465
|
+
if T_margin is not None and use_desired_irradiance_smoothing == False:
|
|
466
|
+
raise RuntimeError("T_margin can only be used when use_desired_irradiance_smoothing is True.")
|
|
467
|
+
def merit_function()->torch.Tensor:
|
|
468
|
+
"""
|
|
469
|
+
"""
|
|
470
|
+
x,weights,y,wl = trace_to_detector(optical_system,sequence,source,detector,num_rays,device,method_ray_tracing=method_ray_tracing)
|
|
471
|
+
Qval = source.get_flux(x)
|
|
472
|
+
|
|
473
|
+
#print("total energy rays:",(Qval*weights).sum())
|
|
474
|
+
|
|
475
|
+
if smoother.smoothed_desired_irradiance is None and use_desired_irradiance_smoothing == True:
|
|
476
|
+
raise RuntimeError("Using desired irradiance smoothing but smoothed_desired_irradiance was not provided!--calc_smooth_desired_irradiance")
|
|
477
|
+
|
|
478
|
+
smoother.smoother_rect_special = None
|
|
479
|
+
if T_margin is not None and use_desired_irradiance_smoothing == True:
|
|
480
|
+
xmin = y[:,0].min().item()
|
|
481
|
+
xmax = y[:,0].max().item()
|
|
482
|
+
ymin = y[:,1].min().item()
|
|
483
|
+
ymax = y[:,1].max().item()
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
#xlen = xmax-xmin
|
|
487
|
+
#ylen = ymax-ymin
|
|
488
|
+
#xmid = (xmax+xmin)*0.5
|
|
489
|
+
#ymid = (ymax+ymin)*0.5
|
|
490
|
+
x_range = [xmin-T_margin,xmax+T_margin]
|
|
491
|
+
y_range = [ymin-T_margin,ymax+T_margin]
|
|
492
|
+
|
|
493
|
+
smoother.smoother_rect_special = [x_range,y_range]
|
|
494
|
+
|
|
495
|
+
smoothed_irradiance = smoother.smoothed_irradiance(y,Qval*weights,x_range,y_range)
|
|
496
|
+
|
|
497
|
+
smoother.smoothed_desired_irradiance = calc_smooth_desired_irradiance(smoother.new_desired_irradiance_fun,
|
|
498
|
+
x_range,y_range,
|
|
499
|
+
smoother.x_grid_size,
|
|
500
|
+
smoother.y_grid_size,
|
|
501
|
+
sigma=smoother.sigma,
|
|
502
|
+
num_integration_points=smoother.smoothed_num_integration_points,
|
|
503
|
+
num_splits=smoother.smoothed_num_splits,
|
|
504
|
+
dtype=smoother.dtype,
|
|
505
|
+
device=smoother.device).detach().to(device=device)
|
|
506
|
+
residual = smoothed_irradiance.reshape(-1)-smoother.smoothed_desired_irradiance.reshape(-1)
|
|
507
|
+
smoother.last_smoothed_irradiance = smoothed_irradiance.detach().cpu()
|
|
508
|
+
|
|
509
|
+
return torch.sqrt(smoother.integrate_values(residual**2,x_range,y_range))
|
|
510
|
+
|
|
511
|
+
else:
|
|
512
|
+
smoothed_irradiance = smoother.smoothed_irradiance(y,Qval*weights)
|
|
513
|
+
|
|
514
|
+
smoother.last_smoothed_irradiance = smoothed_irradiance.detach().cpu()
|
|
515
|
+
residual = None
|
|
516
|
+
|
|
517
|
+
smoother.smoothed_desired_irradiance = smoother.smoothed_desired_irradiance.to(device=device)
|
|
518
|
+
smoother.discrete_desired_irradiance = smoother.discrete_desired_irradiance.to(device=device)
|
|
519
|
+
|
|
520
|
+
if use_desired_irradiance_smoothing:
|
|
521
|
+
#print("sums 1: ",smoothed_irradiance.sum()," 2: ",smoother.smoothed_desired_irradiance.sum())
|
|
522
|
+
residual = smoothed_irradiance.reshape(-1)-smoother.smoothed_desired_irradiance.reshape(-1)#.to(device=device)
|
|
523
|
+
else:
|
|
524
|
+
#print("sums 1: ",smoothed_irradiance.sum()," 2: ",smoother.discrete_desired_irradiance.sum())
|
|
525
|
+
residual = smoothed_irradiance.reshape(-1)-smoother.discrete_desired_irradiance.reshape(-1)#.to(device=device)
|
|
526
|
+
|
|
527
|
+
return torch.sqrt(smoother.integrate_values(residual**2))
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
return merit_function
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
from typing import Callable
|
|
4
|
+
import torch
|
|
5
|
+
import torch
|
|
6
|
+
import math
|
|
7
|
+
import numpy as np
|
|
8
|
+
from .integrators import Cube
|
|
9
|
+
import torch
|
|
10
|
+
from typing import Callable,List,Tuple,Optional
|
|
11
|
+
import gc
|
|
12
|
+
import torch
|
|
13
|
+
import numpy as np
|
|
14
|
+
from .element import trace_to_detector,SequentialOpticalSystem
|
|
15
|
+
import math
|
|
16
|
+
from .source import LightSource
|
|
17
|
+
from .target_grid import Grid
|
|
18
|
+
import gc
|
|
19
|
+
import warnings
|
|
20
|
+
from .render import binned_irradiance
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HatSmoother:
|
|
25
|
+
def __init__(self,
|
|
26
|
+
x_range:list,
|
|
27
|
+
y_range:list,
|
|
28
|
+
x_grid_size:int,
|
|
29
|
+
y_grid_size:int,
|
|
30
|
+
desired_irradiance_fun:Callable,
|
|
31
|
+
dtype=torch.get_default_dtype(),
|
|
32
|
+
device=torch.get_default_device()):
|
|
33
|
+
self.x_range = x_range
|
|
34
|
+
self.y_range = y_range
|
|
35
|
+
self.x_grid_size = x_grid_size
|
|
36
|
+
self.y_grid_size = y_grid_size
|
|
37
|
+
self.desired_irradiance_fun = desired_irradiance_fun
|
|
38
|
+
self.dtype = dtype
|
|
39
|
+
self.device = device
|
|
40
|
+
|
|
41
|
+
self.grid = Grid(x_range,y_range,x_grid_size,y_grid_size)
|
|
42
|
+
centers = self.grid.get_pixel_centers().reshape(-1,2)
|
|
43
|
+
self.discrete_desired_irradiance:torch.Tensor = desired_irradiance_fun(centers).reshape(y_grid_size,x_grid_size)
|
|
44
|
+
|