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
diffinytrace/source.py ADDED
@@ -0,0 +1,661 @@
1
+ # Copyright (c) 2025 Martin Pflaum
2
+ # This file is part of the diffinytrace project, licensed under the MIT License.
3
+
4
+ __all__ = [
5
+ "RaySource",
6
+ "LightSource",
7
+ "PlaneSource",
8
+ "PlaneSource1D",
9
+ "make_cone_directions",
10
+ "VisibleSunlightSimpleMonochromatic",
11
+ "VisibleSunlightSimple",
12
+ "CollimatedMonochromatic",
13
+ "CollimatedGaussianBeam",
14
+ "CollimatedMonochromatic1D",
15
+ "CollimatedMonochromatic1DRotSym",
16
+ ]
17
+
18
+ import torch
19
+ import torch.nn as nn
20
+ import numpy as np
21
+ import math
22
+ from .plotting import Plotable
23
+ from .integrators import Disc,Cube
24
+ from .spectrum import VisibleSunlight_am15g
25
+ from .physical_object import PhysicalObject
26
+
27
+
28
+ class RaySource(PhysicalObject):
29
+ """
30
+ The RaySource is a the base class off all Objects emitting rays. It has a sample function which
31
+ samples points. These points are used to initialize the rays with the forward function.
32
+ """
33
+ def __init__(self,transform):
34
+ """
35
+ Args:
36
+ transform (Transform): The transformation object to be used for the ray source.
37
+ """
38
+ PhysicalObject.__init__(self)
39
+ self.transform = transform
40
+
41
+ def sample(self,num_points,method):
42
+ """
43
+ Samples points in the parameter space of the ray source.
44
+ Args:
45
+ num_points (int): The number of points to sample.
46
+ method (str): The sampling method to use. Can be "sobol", "monte_carlo", etc.
47
+ Returns:
48
+ tuple: A tuple containing the sampled points and their weights.
49
+ """
50
+ raise NotImplementedError("sample() not implemented")
51
+
52
+ def forward(self,x,n_func_enviroment):
53
+
54
+ raise NotImplementedError("forward() not implemented")
55
+
56
+ def get_plot_points_2D(self,resolution):
57
+ print("get_plot_points_2D not implemented")
58
+ return []
59
+
60
+ def get_plot_points_3D(self,resolution):
61
+ print("get_plot_points_3D not implemented")
62
+ return []
63
+
64
+ def get_volume(self):
65
+ raise NotImplementedError("area not implemented")
66
+
67
+ def get_transform(self):
68
+ return self.transform
69
+
70
+
71
+
72
+ class LightSource(RaySource,Plotable):
73
+ r"""
74
+ In our model of an optical system, the light source is composed of the following components:
75
+
76
+ 1. **Light Source Domain**
77
+ The *light source domain* :math:`\Omega \subseteq \mathbb{R}^N` represents the spatial domain of the light source.
78
+
79
+ 2. **Global Ray Initialization Function**
80
+ The *global ray initialization function* :math:`f: \mathbb{R}^N \times \mathbb{R}^{4 \times 4} \to \mathbb{R}^7` is responsible for initializing the ray's position :math:`O`, direction :math:`D`, and wavelength :math:`\lambda` using a point :math:`x \in \Omega` from the *light source domain* :math:`\Omega`. It consists of:
81
+
82
+ - An affine transformation :math:`M \in \mathbb{R}^{4 \times 4}`.
83
+ - A *local ray initialization function* :math:`\hat{f}: \mathbb{R}^N \to \mathbb{R}^7`.
84
+
85
+ The *global ray initialization function* is defined by:
86
+
87
+ .. math::
88
+
89
+ f(x, M) :=
90
+ \begin{bmatrix}
91
+ M \begin{bmatrix}\hat{O} \\ 1\end{bmatrix} \\
92
+ M \begin{bmatrix}\hat{D} \\ 0\end{bmatrix} \\
93
+ \lambda
94
+ \end{bmatrix},
95
+
96
+ where:
97
+
98
+ .. math::
99
+
100
+ \begin{bmatrix}\hat{O} \\ \hat{D} \\ \lambda \end{bmatrix} = \hat{f}(x).
101
+
102
+ 3. **Flux Function**
103
+ The *flux function* :math:`Q: \mathbb{R}^N \to \mathbb{R}` assigns a flux weight to each point :math:`x \in \Omega`.
104
+
105
+ The *ray initialization function* bridges the gap between the *light source domain* and the actual ray tracer. This abstraction is also very helpful for implementation, as it allows for modular design. Following this principle, one does not sample rays in our library, but points and weights according to an integration rule. The light source classes also implement a forward function which is equivalent to the *ray initialization function*.
106
+
107
+ Example:
108
+ >>> import diffinytrace as dit
109
+ >>> wave_len = 1.0
110
+ >>> radius = 8.0
111
+ >>> num_rays = 100
112
+ >>> transform = dit.transforms.Identity()
113
+ >>> light_source = dit.source.CollimatedMonochromatic(transform, radius, wave_len)
114
+ >>> x, weights = light_source.sample(num_rays, "sobol")
115
+ >>> O, D, wls, _, meta_data = light_source(x, n_func_enviroment=dit.materials["AIR"])
116
+
117
+ In the following, we introduce two types of light source models along with their corresponding *light source domains* and *local ray initialization functions*.
118
+
119
+ 1. **Collimated Monochromatic Light Source**
120
+ For this light source model, rays originate from a planar aperture with a uniform direction and a single wavelength. An example for light sources which can be modeled as collimated monochromatic light sources are lasers (see :cite:`dickey2018laser`).
121
+
122
+ - The *light source domain* :math:`\Omega_{CO} \subseteq \mathbb{R}^2` can take various shapes.
123
+ - The *local ray initialization function* is, given a single wavelength :math:`\lambda`, always in the form of:
124
+
125
+ .. math::
126
+
127
+ \hat{f}(x, \lambda)_{CO} =
128
+ \begin{bmatrix}
129
+ x_1 \\
130
+ x_2 \\
131
+ 0 \\
132
+ 0 \\
133
+ 0 \\
134
+ 1 \\
135
+ \lambda
136
+ \end{bmatrix}.
137
+
138
+ - The *flux function* must align with the *light source domain* :math:`\Omega_{CO}`.
139
+
140
+ Examples of *light source domains* for collimated monochromatic light sources include:
141
+
142
+ - **Round Domains**
143
+ Defined by:
144
+
145
+ .. math::
146
+
147
+ \Omega^{round(\hat{r})}_{CO} = \left\{x : ||x||_2 \leq \hat{r}\right\} \subseteq \mathbb{R}^2,
148
+
149
+ where :math:`\hat{r}` is the radius.
150
+
151
+ Example:
152
+ >>> import diffinytrace as dit
153
+ >>> wl = 0.5
154
+ >>> r = 8.0
155
+ >>> transform = dit.transforms.Identity()
156
+ >>> light_source = dit.source.CollimatedMonochromatic(transform, r, wl)
157
+
158
+ - **Square Domains**
159
+ Defined by:
160
+
161
+ .. math::
162
+
163
+ \Omega^{square(\hat{d})}_{CO} = \left\{x : ||x||_\infty \leq \hat{d}\right\} \subseteq \mathbb{R}^2,
164
+
165
+ where :math:`\hat{d}` is the half diameter.
166
+
167
+ 2. **Monochromatic Light Sources with Etendue**
168
+ Real-world physical light sources are characterized by a finite area-angle product, corresponding to a finite etendue (see :cite:`rausch2017illumination`). In this model, rays originate from a planar surface with a specific wavelength, but the directions differ.
169
+
170
+ - The *light source domain* :math:`\Omega_{ET} \subseteq \mathbb{R}^4` has four dimensions.
171
+ - The *flux function* maps a 4-dimensional vector to a single value.
172
+ - The *local ray initialization function* maps a 4-dimensional vector to a 7-dimensional vector.
173
+
174
+ Example: **Pillbox Sunlight Model**
175
+ The sun has an etendue, meaning both an angular and spatial extent. The sunshape describes how solar radiation is distributed across the solar disk, represented by the normalized radiance profile :math:`\hat{L}(\theta)`:
176
+
177
+ .. math::
178
+
179
+ \hat{L}_{\text{pillbox}}(\theta) =
180
+ \begin{cases}
181
+ L_1 & \text{if } 0 \leq \theta \leq \theta_{disc}, \\
182
+ 0 & \text{if } \theta > \theta_{disc}.
183
+ \end{cases}
184
+
185
+ Here, :math:`\theta` is the angular displacement of a ray compared to the general direction of the sun. Typically, :math:`\theta_{disc} = 4.65 \, \text{mrad}` is used (see :cite:`pillbox_sunlight`).
186
+
187
+ The *light source domain* for the pillbox sunlight model is defined as:
188
+
189
+ .. math::
190
+
191
+ \Omega^{pillbox(\hat{d})}_{ET} := \left\{x : ||[x_1, x_2]^T||_\infty \leq \hat{d}, ||[x_3, x_4]^T||_\infty \leq 1\right\} \subseteq \mathbb{R}^4,
192
+
193
+ where :math:`\hat{d}` is the half diameter.
194
+
195
+ The *local ray initialization function* is:
196
+
197
+ .. math::
198
+
199
+ \hat{f}(x)_{ET}^{pillbox} :=
200
+ \begin{bmatrix}
201
+ x_1 \\
202
+ x_2 \\
203
+ 0 \\
204
+ \sin(\theta(x_3))\cos(\phi(x_4)) \\
205
+ \sin(\theta(x_3))\sin(\phi(x_4)) \\
206
+ \cos(\theta(x_3)) \\
207
+ \lambda
208
+ \end{bmatrix},
209
+
210
+ where:
211
+
212
+ .. math::
213
+
214
+ \theta(x_3) := -\theta_{disc} + 2\theta_{disc}x_3, \quad \phi(x_4) := 2\pi x_4.
215
+
216
+ The *flux function* is:
217
+
218
+ .. math::
219
+
220
+ Q_{ET}(x)_{pillbox} := \frac{1 \, \text{W}}{|\Omega^{pillbox(\hat{d})}_{ET}|}.
221
+ """
222
+ def __init__(self,transform,integrator,flux_func=None,total_power=1.0,num_points_normalize=700000,method_normalize="sobol"):
223
+ """
224
+ Args:
225
+ transform (Transform): The transformation object to be used for the light source.
226
+ integrator (Integrator): The integrator object to be used for sampling.
227
+ flux_func (callable, optional): A function that returns the flux at a given point. Defaults to None.
228
+ total_power (float, optional): The total power of the light source. Defaults to 1.0.
229
+ num_points_normalize (int, optional): The number of points to use for normalization. Defaults to 700000.
230
+ method_normalize (str, optional): The sampling method to use for normalization. Defaults to "sobol".
231
+ """
232
+ Plotable.__init__(self,"#f8cecc","#b85450")
233
+ RaySource.__init__(self,transform)
234
+ self.total_power = total_power
235
+ self.integrator = integrator
236
+ self.norm_val = None
237
+ if flux_func is None:
238
+ def t_flux_func(x):
239
+ device = x.device
240
+ dtype = x.dtype
241
+ return torch.ones(x.shape[0],device=device,dtype=dtype)/self.get_volume()
242
+ flux_func = t_flux_func
243
+ self.norm_val = 1.0
244
+
245
+ self._flux_func = flux_func
246
+
247
+ if self.norm_val is None:
248
+ points,weights = self.integrator.sample(num_points_normalize,method_normalize)
249
+ self.norm_val = torch.sum(self._flux_func(points)*weights).cpu().item()
250
+
251
+ def sample(self,num_points,method):
252
+ return self.integrator.sample(num_points,method)
253
+
254
+ def get_flux(self,x):
255
+ return self._flux_func(x)*(self.total_power/self.norm_val)
256
+
257
+ def get_volume(self):
258
+ return self.integrator.get_volume()
259
+
260
+ class PlaneSource(LightSource):
261
+ def __init__(self,transform,aperture_radius,integrator,is_square=False,flux_func=None,total_power=1.0,num_points_normalize=700000,method_normalize="sobol"):
262
+ super().__init__(transform,integrator,flux_func,total_power,num_points_normalize,method_normalize)
263
+ self.aperture_radius = aperture_radius
264
+ self.is_square = is_square
265
+
266
+ def get_plot_points_2D(self,resolution):
267
+ aperture_radius = self.aperture_radius
268
+ x = None
269
+ y = None
270
+
271
+ y = torch.linspace(-aperture_radius,aperture_radius,resolution)
272
+ x = torch.zeros_like(y)
273
+
274
+ tmpx,_ = self.sample(1,"sobol")
275
+ x_integrator = torch.zeros((x.shape[0],tmpx.shape[1]))
276
+ x_integrator[:,0] = x
277
+ x_integrator[:,1] = y
278
+ z = None
279
+ with torch.no_grad():
280
+ O2,D2,wl,_,_ = self(x_integrator,None)
281
+
282
+ x = O2[:,0].detach().reshape(-1)
283
+ y = O2[:,1].detach().reshape(-1)
284
+ z = O2[:,2].detach().reshape(-1)
285
+
286
+ if not self.is_square:
287
+ mul = (torch.sqrt(x*x+y*y)>aperture_radius).float()/torch.sqrt(x*x+y*y)*aperture_radius
288
+ mul += (torch.sqrt(x*x+y*y)<aperture_radius).float()
289
+ x = x*mul
290
+ y = y*mul
291
+
292
+ transform=self.transform
293
+ v = torch.zeros((x.shape[0],4))
294
+ v[:,0] = x
295
+ v[:,1] = y
296
+ v[:,2] = z
297
+ v[:,3] = torch.ones_like(v[:,3])
298
+
299
+ Mv = None
300
+ with torch.no_grad():
301
+ M = transform.get_transformation_matrix().detach()
302
+ Mv = v@M.T
303
+ x = Mv[:,0].reshape(-1)
304
+ y = Mv[:,1].reshape(-1)
305
+ z = Mv[:,2].reshape(-1)
306
+ return [(z,y)]
307
+
308
+ def get_plot_points_3D(self,resolution):
309
+ aperture_radius = self.aperture_radius
310
+ x = None
311
+ y = None
312
+
313
+ _x = torch.linspace(-aperture_radius,aperture_radius,resolution)
314
+ _y = torch.linspace(-aperture_radius,aperture_radius,resolution)
315
+ mesh = torch.meshgrid(_x,_y)
316
+ x = mesh[0].reshape(-1)
317
+ y = mesh[1].reshape(-1)
318
+
319
+ tmpx,_ = self.sample(1,"sobol")
320
+ x_integrator = torch.zeros((x.shape[0],tmpx.shape[1]))
321
+ x_integrator[:,0] = x
322
+ x_integrator[:,1] = y
323
+ z = None
324
+ with torch.no_grad():
325
+ O2,D2,wl,_,_ = self(x_integrator,None)
326
+
327
+ x = O2[:,0].detach().reshape(-1)
328
+ y = O2[:,1].detach().reshape(-1)
329
+ z = O2[:,2].detach().reshape(-1)
330
+
331
+ if not self.is_square:
332
+ mul = (torch.sqrt(x*x+y*y)>aperture_radius).float()/torch.sqrt(x*x+y*y)*aperture_radius
333
+ mul += (torch.sqrt(x*x+y*y)<aperture_radius).float()
334
+ x = x*mul
335
+ y = y*mul
336
+
337
+ transform=self.transform
338
+ v = torch.zeros((x.shape[0],4))
339
+ v[:,0] = x
340
+ v[:,1] = y
341
+ v[:,2] = z
342
+ v[:,3] = torch.ones_like(v[:,3])
343
+
344
+ Mv = None
345
+ with torch.no_grad():
346
+ M = transform.get_transformation_matrix().detach()
347
+ Mv = v@M.T
348
+ x = Mv[:,0].reshape(resolution,resolution)
349
+ y = Mv[:,1].reshape(resolution,resolution)
350
+ z = Mv[:,2].reshape(resolution,resolution)
351
+ return [(x,y,z)]
352
+
353
+
354
+ class PlaneSource1D(LightSource):
355
+ def __init__(self,transform,aperture_radius,integrator,flux_func,total_power=1.0,num_points_normalize=700000,method_normalize="sobol"):
356
+ super().__init__(transform,integrator,flux_func,total_power,num_points_normalize,method_normalize)
357
+ self.aperture_radius = aperture_radius
358
+
359
+ def get_plot_points_2D(self,resolution):
360
+ aperture_radius = self.aperture_radius
361
+
362
+ transform=self.transform
363
+ v = torch.zeros((resolution,4))
364
+ v[:,1] = torch.linspace(-aperture_radius,aperture_radius,resolution)
365
+ v[:,3] = torch.ones_like(v[:,3])
366
+
367
+ Mv = None
368
+ with torch.no_grad():
369
+ M = transform.get_transformation_matrix().detach()
370
+ Mv = v@M.T
371
+
372
+ x = Mv[:,0].reshape(-1)
373
+ y = Mv[:,1].reshape(-1)
374
+ z = Mv[:,2].reshape(-1)
375
+ return [(z,y)]
376
+
377
+ def get_plot_points_3D(self,resolution):
378
+ raise RuntimeError("get_plot_points_3D is not implemented for PlaneSource1D")
379
+
380
+ def make_cone_directions(num_rays, unif1, unif2, theta_max_rad):
381
+ """
382
+ Sample directions uniformly within a cone of angular radius `theta_max_rad` centered on the z-axis.
383
+
384
+ Parameters:
385
+ - num_rays (int): Number of direction vectors to sample within the cone.
386
+ - unif1 (torch.Tensor): Tensor of uniform samples for cos(theta) sampling.
387
+ - unif2 (torch.Tensor): Tensor of uniform samples for the azimuthal angle sampling.
388
+ - theta_max_rad (float): Angular radius of the cone in radians.
389
+
390
+ Returns:
391
+ - directions (torch.Tensor): Tensor of shape (num_rays, 3), with each row a direction vector.
392
+ """
393
+ # Ensure uniform samples are tensors and match the number of rays
394
+ if unif1.shape[0] != num_rays or unif2.shape[0] != num_rays:
395
+ raise ValueError("Length of unif1 and unif2 must match num_rays.")
396
+ theta_max_rad = torch.tensor(theta_max_rad)
397
+ # Uniform sampling of cos(theta) within the cone angle
398
+ thetas = -theta_max_rad + unif1 * (theta_max_rad*2.0)
399
+
400
+ # Uniform sampling of azimuthal angle phi
401
+ phi = (2.0 * torch.pi) * unif2
402
+
403
+ # Spherical to Cartesian conversion (cone aligned with z-axis)
404
+ x = torch.sin(thetas) * torch.cos(phi)
405
+ y = torch.sin(thetas) * torch.sin(phi)
406
+ z = torch.cos(thetas)
407
+
408
+ # Combine into a directions tensor
409
+ directions = torch.stack((x, y, z), dim=1)
410
+
411
+ return directions
412
+
413
+
414
+ """
415
+
416
+ import numpy as np
417
+ import matplotlib.pyplot as plt
418
+ from mpl_toolkits.mplot3d import Axes3D
419
+
420
+ num_rays = 20
421
+ directions = sample_cone_directions(num_rays)
422
+
423
+ # Plotting the sampled directions as lines in 3D
424
+ fig = plt.figure(figsize=(8, 6))
425
+ ax = fig.add_subplot(111, projection='3d')
426
+
427
+ # Plot lines from the origin to each sampled direction
428
+ for direction in directions:
429
+ ax.plot([0, direction[0]], [0, direction[1]], [0, direction[2]], color='blue', alpha=0.6)
430
+
431
+ ax.set_xlabel("X")
432
+ ax.set_ylabel("Y")
433
+ ax.set_zlabel("Z")
434
+ ax.set_title("Sampled Directions in a 4.65 mrad Cone Centered Around the Z-axis")
435
+ ax.view_init(elev=20, azim=30) # Adjust viewing angle for better visualization
436
+ plt.show()
437
+
438
+
439
+ """
440
+ class VisibleSunlightSimpleMonochromatic(PlaneSource):
441
+ """
442
+ A class representing a visible sunlight source with a monochromatic spectrum.
443
+ It also has a cone of 4.65 mrad."""
444
+
445
+ def __init__(self,transform,aperture_radius,wl=0.5,is_square=True,total_power=1.0,theta_max_rad=4.65/1000.):
446
+ """
447
+ cube1 = Cube([[-aperture_radius,aperture_radius],[-aperture_radius,aperture_radius]])
448
+ cube2 = Cube([[0.,1.],[0.,1.]])
449
+
450
+ integrator = Compose([cube1,cube2])
451
+
452
+ """
453
+
454
+ integrator = Cube([[-aperture_radius,aperture_radius],[-aperture_radius,aperture_radius],[0.,1.],[0.,1.]])
455
+ self.theta_max_rad = theta_max_rad
456
+ self.wl = wl
457
+ PlaneSource.__init__(self,transform,aperture_radius,integrator,is_square,None,total_power)
458
+
459
+ def sample(self,num_points,method="monte_carlo"):
460
+ if not ((method == "sobol_pow2")or (method == "sobol") or (method == "monte_carlo")):
461
+ print("Only sobol_pow2,sobol or monte_carlo sampling supported for VisibleSunlightSimpleMonochromatic")
462
+ return self.integrator.sample(num_points,method)
463
+
464
+ def forward(self,x,n_func_enviroment):
465
+ N = x.shape[0]
466
+ device = x.device
467
+ dtype = x.dtype
468
+ O1 = torch.zeros(N,3,device=device,dtype=dtype)
469
+ O1[:,[0,1]] = x[:,[0,1]]
470
+ O1[:,-1] = 0.0
471
+ D1 = make_cone_directions(N, x[:,2], x[:,3], self.theta_max_rad)
472
+ #print(D1)
473
+ wl = torch.ones((N),device=device,dtype=dtype)*self.wl
474
+ #self.spectrum(x[4])
475
+
476
+ #TODO inconsitent with plotting!!S
477
+ O2 = self.transform.to_global_pos(O1)
478
+ D2 = self.transform.to_global_dir(D1)
479
+
480
+ ray_paths = [O2.detach()]
481
+
482
+ valid = self.integrator.in_bounds(x)
483
+ PL,OPL = 0.0,0.0
484
+ meta_data = {}
485
+ meta_data["PL"],meta_data["OPL"],meta_data["ray_paths"], meta_data["valid"] = PL, OPL, ray_paths, valid
486
+ return O2,D2,wl,n_func_enviroment,meta_data
487
+
488
+
489
+
490
+ class VisibleSunlightSimple(PlaneSource):
491
+ """
492
+ A class representing a visible sunlight source with a spectrum acording to the visble sunlight.
493
+ It also has a an etendue with cone of 4.65 mrad."""
494
+ def __init__(self,transform,aperture_radius,is_square=True,total_power=1.0):
495
+ self.spectrum = VisibleSunlight_am15g()
496
+ self.aperture_radius = aperture_radius
497
+ integrator = Cube([[-aperture_radius,aperture_radius],[-aperture_radius,aperture_radius],[0.,1.],[0.,1.],self.spectrum.bounds])
498
+ PlaneSource.__init__(self,transform,aperture_radius,integrator,is_square,None,total_power)
499
+ self.theta_max_mrad = 4.65
500
+
501
+
502
+ def sample(self,num_points,method="monte_carlo"):
503
+ if not ((method == "sobol_pow2")or (method == "sobol") or (method == "monte_carlo")):
504
+ raise RuntimeError("Only sobol_pow2,sobol or monte_carlo sampling supported for VisibleSunlightSimple")
505
+ return self.integrator.sample(num_points,method)
506
+
507
+
508
+ def forward(self,x,n_func_enviroment):
509
+ N = x.shape[0]
510
+ device = x.device
511
+ dtype = x.dtype
512
+ O1 = torch.zeros(N,3,device=device,dtype=dtype)
513
+ O1[:,[0,1]] = x[:,[0,1]]
514
+ O1[:,-1] = 0.0
515
+
516
+ D1 = make_cone_directions(N, x[:,2], x[:,3], self.theta_max_mrad)
517
+ wl = self.spectrum.bounds[0]+(self.spectrum.bounds[1]-self.spectrum.bounds[0])*x[:,4]
518
+ #self.spectrum(x[4])
519
+
520
+ #TODO inconsitent with plotting!!S
521
+ O2 = self.transform.to_global_pos(O1)
522
+ D2 = self.transform.to_global_dir(D1)
523
+
524
+ ray_paths = [O2.detach()]
525
+
526
+ valid = self.integrator.in_bounds(x)
527
+ PL,OPL = 0.0,0.0
528
+ meta_data = {}
529
+ meta_data["PL"],meta_data["OPL"],meta_data["ray_paths"], meta_data["valid"] = PL, OPL, ray_paths, valid
530
+ return O2,D2,wl,n_func_enviroment,meta_data
531
+
532
+
533
+ class CollimatedMonochromatic(PlaneSource):
534
+ """
535
+ A class representing a collimated monochromatic light source.
536
+ This class is a subclass of PlaneSource and is used to generate rays
537
+ with a specific wavelength and a collimated beam profile.
538
+ """
539
+ def __init__(self,transform,aperture_radius,wl,is_square=False,flux_func=None,total_power=1.0):
540
+ integrator = None
541
+ aperture_radius = abs(aperture_radius)
542
+ if is_square:
543
+ integrator = Cube([[-aperture_radius,aperture_radius],[-aperture_radius,aperture_radius]])
544
+ else:
545
+ integrator = Disc(aperture_radius)
546
+ super().__init__(transform,aperture_radius,integrator,is_square,flux_func,total_power)
547
+ self.wl = wl
548
+
549
+
550
+ def sample(self,num_points,method="monte_carlo")->torch.Tensor:
551
+ return self.integrator.sample(num_points,method)
552
+
553
+ def forward(self,x,n_func_enviroment):
554
+ N = x.shape[0]
555
+ device = x.device
556
+ dtype = x.dtype
557
+ O1 = torch.zeros(N,3,device=device,dtype=dtype)
558
+ O1[:,[0,1]] = x
559
+ O1[:,-1] = 0.0
560
+
561
+ D1 = torch.zeros_like(O1)
562
+ D1[:,-1] = 1.0
563
+ PL,OPL = 0.0,0.0
564
+ #TODO inconsitent with plotting!!S
565
+ O2 = self.transform.to_global_pos(O1)
566
+ D2 = self.transform.to_global_dir(D1)
567
+
568
+ ray_paths = [O2.detach()]
569
+
570
+ valid = self.integrator.in_bounds(x)
571
+
572
+ wl = torch.ones(N,device=device,dtype=dtype)*self.wl
573
+ meta_data = {}
574
+ meta_data["PL"],meta_data["OPL"],meta_data["ray_paths"], meta_data["valid"] = PL, OPL, ray_paths, valid
575
+ return O2,D2,wl,n_func_enviroment,meta_data
576
+
577
+
578
+ class CollimatedGaussianBeam(CollimatedMonochromatic):
579
+ def __init__(self,transform,aperture_radius,wl,gaussian_constant,total_power=1.0):
580
+ def flux_func(x):
581
+ rho = torch.norm(x,dim=1)
582
+ return torch.exp(-gaussian_constant*(rho**2.0))
583
+ super().__init__(transform,aperture_radius,wl,is_square=False,flux_func=flux_func,total_power=total_power)
584
+
585
+ class CollimatedMonochromatic1D(PlaneSource1D):
586
+ """
587
+ A class representing a collimated monochromatic light source.
588
+ This class is a subclass of PlaneSource1D and is used to generate rays
589
+ with a specific wavelength and a collimated beam profile."""
590
+ def __init__(self,transform,aperture_radius,wl,flux_func=None,total_power=1.0):
591
+ self.wl = wl
592
+ integrator = Cube([[-aperture_radius,aperture_radius]])
593
+ super().__init__(transform,aperture_radius,integrator,flux_func,total_power)
594
+
595
+ def sample(self,num_points,method="monte_carlo"):
596
+ return self.integrator.sample(num_points,method)
597
+
598
+ def forward(self,x,n_func_enviroment):
599
+ N = x.shape[0]
600
+ device = x.device
601
+ dtype = x.dtype
602
+ O1 = torch.zeros(N,3,device=device,dtype=dtype)
603
+ O1[:,1] = x[:,0]
604
+ O1[:,-1] = 0.0
605
+
606
+ D1 = torch.zeros_like(O1)
607
+ D1[:,-1] = 1.0
608
+ PL,OPL = 0.0,0.0
609
+
610
+ O2 = self.transform.to_global_pos(O1)
611
+ D2 = self.transform.to_global_dir(D1)
612
+
613
+ ray_paths = [O2.detach()]
614
+ valid = self.integrator.in_bounds(x)
615
+ wl = torch.ones(N,device=device,dtype=dtype)*self.wl
616
+
617
+ meta_data = {}
618
+ meta_data["PL"],meta_data["OPL"],meta_data["ray_paths"], meta_data["valid"] = PL, OPL, ray_paths, valid
619
+ return O2,D2,wl,n_func_enviroment,meta_data
620
+
621
+
622
+ class CollimatedMonochromatic1DRotSym(PlaneSource1D):
623
+ """
624
+ A class representing a collimated monochromatic light source.
625
+ This class is a subclass of PlaneSource1D and is used to generate rays
626
+ with a specific wavelength and a collimated beam profile."""
627
+ def __init__(self,transform,aperture_radius,wl,flux_func=None,total_power=1.0):
628
+ self.wl = wl
629
+ integrator = Cube([[0,1]])
630
+ super().__init__(transform,aperture_radius,integrator,flux_func,total_power)
631
+
632
+ def sample(self,num_points,method="monte_carlo"):
633
+ return self.integrator.sample(num_points,method)
634
+
635
+ def forward(self,x,n_func_enviroment):
636
+ N = x.shape[0]
637
+ device = x.device
638
+ dtype = x.dtype
639
+ O1 = torch.zeros(N,3,device=device,dtype=dtype)
640
+ #print(self.aperture_radius,torch.sqrt(x[:,0]))
641
+ O1[:,1] = torch.sqrt(x[:,0]) * self.aperture_radius
642
+ O1[:,-1] = 0.0
643
+
644
+ D1 = torch.zeros_like(O1)
645
+ D1[:,-1] = 1.0
646
+ PL,OPL = 0.0,0.0
647
+
648
+ O2 = self.transform.to_global_pos(O1)
649
+ D2 = self.transform.to_global_dir(D1)
650
+
651
+ ray_paths = [O2.detach()]
652
+ valid = self.integrator.in_bounds(x)
653
+ wl = torch.ones(N,device=device,dtype=dtype)*self.wl
654
+
655
+ meta_data = {}
656
+ meta_data["PL"],meta_data["OPL"],meta_data["ray_paths"], meta_data["valid"] = PL, OPL, ray_paths, valid
657
+ return O2,D2,wl,n_func_enviroment,meta_data
658
+
659
+
660
+
661
+