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.
Files changed (38) hide show
  1. diffinytrace/__init__.py +122 -0
  2. diffinytrace/basis_functions/__init__.py +14 -0
  3. diffinytrace/basis_functions/bspline.py +521 -0
  4. diffinytrace/basis_functions/chebyshev.py +3 -0
  5. diffinytrace/basis_functions/legendre.py +77 -0
  6. diffinytrace/basis_functions/zernike.py +235 -0
  7. diffinytrace/config.py +140 -0
  8. diffinytrace/constraints.py +54 -0
  9. diffinytrace/element.py +1660 -0
  10. diffinytrace/export/__init__.py +8 -0
  11. diffinytrace/export/cad.py +253 -0
  12. diffinytrace/gaussian_smoother.py +530 -0
  13. diffinytrace/hat_smoother.py +44 -0
  14. diffinytrace/integrators.py +452 -0
  15. diffinytrace/intersection.py +285 -0
  16. diffinytrace/optimize.py +808 -0
  17. diffinytrace/physical_object.py +150 -0
  18. diffinytrace/plotting/__init__.py +16 -0
  19. diffinytrace/plotting/core.py +92 -0
  20. diffinytrace/plotting/quantity2D.py +188 -0
  21. diffinytrace/plotting/system2D.py +220 -0
  22. diffinytrace/plotting/system3D.py +327 -0
  23. diffinytrace/plotting/wavelength.py +231 -0
  24. diffinytrace/refractive_index.py +101 -0
  25. diffinytrace/render.py +77 -0
  26. diffinytrace/source.py +661 -0
  27. diffinytrace/spectrum.py +79 -0
  28. diffinytrace/surface.py +468 -0
  29. diffinytrace/target_grid.py +399 -0
  30. diffinytrace/transforms.py +472 -0
  31. diffinytrace/utils/__init__.py +7 -0
  32. diffinytrace/utils/autograd.py +116 -0
  33. diffinytrace/utils/irradiance_importer.py +134 -0
  34. diffinytrace-2.1.dist-info/METADATA +26 -0
  35. diffinytrace-2.1.dist-info/RECORD +38 -0
  36. diffinytrace-2.1.dist-info/WHEEL +5 -0
  37. diffinytrace-2.1.dist-info/licenses/LICENSE +21 -0
  38. 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
+