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,452 @@
|
|
|
1
|
+
# Copyright (c) 2025 Martin Pflaum
|
|
2
|
+
# This file is part of the diffinytrace project, licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
__all__ = ["Integrator", "Cube","Disc","IntegrationMethod"]
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
import numpy as np
|
|
8
|
+
import math
|
|
9
|
+
from scipy.stats import qmc
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
mersenne_twister = np.random.Generator(np.random.MT19937(seed=12345))
|
|
13
|
+
|
|
14
|
+
class IntegrationMethod(Enum):
|
|
15
|
+
SIMPSON = "simpson"
|
|
16
|
+
MIDPOINT = "midpoint"
|
|
17
|
+
MONTE_CARLO = "monte_carlo"
|
|
18
|
+
SOBOL = "sobol"
|
|
19
|
+
SOBOL_POW2 = "sobol_pow2"
|
|
20
|
+
|
|
21
|
+
def check_2val(num_points):
|
|
22
|
+
num_points = np.array(num_points)
|
|
23
|
+
if len(num_points.shape)>0:
|
|
24
|
+
return num_points[0]*num_points[1]
|
|
25
|
+
return num_points
|
|
26
|
+
|
|
27
|
+
class Integrator():
|
|
28
|
+
def __init__(self):
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def sample(self, num_points: int | list[int], method: IntegrationMethod) -> tuple[torch.Tensor, torch.Tensor]:
|
|
32
|
+
"""
|
|
33
|
+
Sample points and weights using the specified method.
|
|
34
|
+
Args:
|
|
35
|
+
num_points (int or list): Number of points in each dimension.
|
|
36
|
+
method (str): The integration method to use. Options are 'simpson', 'midpoint', 'monte_carlo', 'sobol', and 'sobol_pow2'.
|
|
37
|
+
Returns:
|
|
38
|
+
tuple: A tuple containing the sampled points and their corresponding weights.
|
|
39
|
+
"""
|
|
40
|
+
raise NotImplementedError("sample() not implemented")
|
|
41
|
+
|
|
42
|
+
def in_bounds(self, x: torch.Tensor) -> torch.Tensor:
|
|
43
|
+
raise NotImplementedError("in_bounds() not implemented")
|
|
44
|
+
|
|
45
|
+
def get_volume(self) -> float:
|
|
46
|
+
raise NotImplementedError("get_volume() not implemented")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Cube(Integrator):
|
|
50
|
+
"""
|
|
51
|
+
Integrator for a multi-dimensional cube (hyperrectangle).
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
bounds (array-like): The bounds for each dimension of the cube. Should be a list or array of shape (n_dim, 2),
|
|
55
|
+
where each row specifies [lower_bound, upper_bound] for a dimension.
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
>>> cube = dit.integrators.Cube([[0, 1], [0, 1]])
|
|
59
|
+
>>> points, weights = cube.sample([10, 10], method=IntegrationMethod.MIDPOINT)
|
|
60
|
+
>>> volume = cube.get_volume()
|
|
61
|
+
>>> all_in_bounds = cube.in_bounds(points)
|
|
62
|
+
>>> print("Sampled points:", points)
|
|
63
|
+
>>> print("Integration weights:", weights)
|
|
64
|
+
>>> print("Cube volume:", volume)
|
|
65
|
+
>>> print("All points in bounds:", all_in_bounds)
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self,bounds):
|
|
69
|
+
super().__init__()
|
|
70
|
+
bounds = np.array(bounds)
|
|
71
|
+
if len(bounds.shape)==1:
|
|
72
|
+
bounds = np.array([bounds])
|
|
73
|
+
|
|
74
|
+
self.bounds = torch.tensor(bounds)
|
|
75
|
+
if len(self.bounds.shape)!=2:
|
|
76
|
+
raise ValueError("len(self.bounds.shape)==2 must hold true!")
|
|
77
|
+
|
|
78
|
+
def sample(self, num_points: int | list[int], method: IntegrationMethod = IntegrationMethod.MIDPOINT) -> tuple[torch.Tensor, torch.Tensor]:
|
|
79
|
+
r"""
|
|
80
|
+
Sample points and weights using the specified method.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
num_points (int or list): Number of points in each dimension.
|
|
84
|
+
method (str): The integration method to use. Options are 'simpson', 'midpoint', 'monte_carlo', 'sobol', and 'sobol_pow2'.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
tuple: A tuple containing the sampled points and their corresponding weights.
|
|
88
|
+
"""
|
|
89
|
+
if not isinstance(method, str):
|
|
90
|
+
method = str(method.value)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if method == 'simpson':
|
|
94
|
+
return self._sample_simpson(num_points)
|
|
95
|
+
elif method == 'midpoint':
|
|
96
|
+
return self._sample_midpoint(num_points)
|
|
97
|
+
elif method == 'monte_carlo':
|
|
98
|
+
return self._sample_monte_carlo(num_points)
|
|
99
|
+
elif method == 'sobol':
|
|
100
|
+
return self._sample_sobol(num_points,False)
|
|
101
|
+
elif method == 'sobol_pow2':
|
|
102
|
+
return self._sample_sobol(num_points,True)
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError(f"Unknown integration method: {method}")
|
|
105
|
+
|
|
106
|
+
def in_bounds(self, x:torch.Tensor) -> torch.Tensor:
|
|
107
|
+
out = torch.ones(x.shape[0],device=x.device,dtype=torch.bool).float()
|
|
108
|
+
for k in range(self.bounds.shape[0]):
|
|
109
|
+
out = out*((self.bounds[k,0]<=x[:,k]).float())*((x[:,k]<=self.bounds[k,1]).float())
|
|
110
|
+
out = out==1.0
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
def get_volume(self) -> float:
|
|
114
|
+
"""
|
|
115
|
+
Returns:
|
|
116
|
+
float: Volume of the Cube.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
volume = torch.prod(self.bounds[:,1]-self.bounds[:,0])
|
|
120
|
+
return volume
|
|
121
|
+
|
|
122
|
+
def _sample_midpoint(self, num_points):
|
|
123
|
+
"""
|
|
124
|
+
Sample points and weights using the midpoint rule.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
num_points (list or array): Number of points in each dimension.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
sampled_points (torch.Tensor): Tensor of sampled points.
|
|
131
|
+
weights (torch.Tensor): Tensor of weights associated with each point.
|
|
132
|
+
"""
|
|
133
|
+
# Ensure num_points matches the number of dimensions in bounds
|
|
134
|
+
num_points = np.array(num_points)
|
|
135
|
+
if len(num_points) != self.bounds.shape[0]:
|
|
136
|
+
raise ValueError("Using midpoint sampling expected num_points to match the number of dimensions.")
|
|
137
|
+
|
|
138
|
+
midpoints = []
|
|
139
|
+
|
|
140
|
+
# Calculate the midpoints for each dimension
|
|
141
|
+
for i in range(self.bounds.shape[0]):
|
|
142
|
+
lower_bound, upper_bound = self.bounds[i]
|
|
143
|
+
# Calculate the size of each interval (dx) for the dimension
|
|
144
|
+
dx = (upper_bound - lower_bound) / num_points[i]
|
|
145
|
+
# Compute the midpoints in this dimension
|
|
146
|
+
points = torch.linspace(lower_bound + dx / 2.0, upper_bound - dx / 2.0, num_points[i])
|
|
147
|
+
midpoints.append(points)
|
|
148
|
+
|
|
149
|
+
# Create a meshgrid of midpoints for all dimensions
|
|
150
|
+
grid = torch.meshgrid(*midpoints, indexing='ij')
|
|
151
|
+
sampled_points = torch.stack(grid, dim=-1).reshape(-1, self.bounds.shape[0])
|
|
152
|
+
|
|
153
|
+
# Compute the weights based on the volume of each subregion
|
|
154
|
+
weights = torch.ones(sampled_points.shape[0], dtype=torch.float32)
|
|
155
|
+
|
|
156
|
+
for i in range(self.bounds.shape[0]):
|
|
157
|
+
lower_bound, upper_bound = self.bounds[i]
|
|
158
|
+
dx = (upper_bound - lower_bound) / num_points[i]
|
|
159
|
+
# Each dimension contributes its own weight
|
|
160
|
+
weights *= dx # Multiply the weights by the width of the intervals
|
|
161
|
+
|
|
162
|
+
return sampled_points, weights
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _sample_simpson(self, num_points):
|
|
166
|
+
# Ensure num_points matches the expected number of dimensions
|
|
167
|
+
num_points = np.array(num_points)
|
|
168
|
+
if len(num_points) != self.bounds.shape[0]:
|
|
169
|
+
raise ValueError("Using Simpson's rule expected num_points to have the same number of entries as dimensions.")
|
|
170
|
+
for elem in num_points:
|
|
171
|
+
if elem % 2 == 0:
|
|
172
|
+
raise ValueError("Simpson's rule only takes an odd number of points!")
|
|
173
|
+
|
|
174
|
+
# Create sample points and weights
|
|
175
|
+
sample_points = []
|
|
176
|
+
weights = []
|
|
177
|
+
|
|
178
|
+
for i in range(self.bounds.shape[0]):
|
|
179
|
+
lower_bound, upper_bound = self.bounds[i]
|
|
180
|
+
dx = (upper_bound - lower_bound) / (num_points[i] - 1)
|
|
181
|
+
x = torch.linspace(lower_bound, upper_bound, num_points[i]) # Include endpoints
|
|
182
|
+
sample_points.append(x)
|
|
183
|
+
|
|
184
|
+
# Weights for Simpson's rule
|
|
185
|
+
w = torch.ones(num_points[i]) # Initialize weights
|
|
186
|
+
w[1:-1:2] *= 4 # Odd indices
|
|
187
|
+
w[2:-1:2] *= 2 # Even indices
|
|
188
|
+
weights.append(w * dx / 3) # Multiply by dx/3
|
|
189
|
+
|
|
190
|
+
# Create a meshgrid of sample points
|
|
191
|
+
grid = torch.meshgrid(*sample_points)
|
|
192
|
+
sampled_points = torch.stack(grid, dim=-1).reshape(-1, self.bounds.shape[0])
|
|
193
|
+
|
|
194
|
+
# Total weight for each point
|
|
195
|
+
total_weights = weights[0]
|
|
196
|
+
for w in weights[1:]:
|
|
197
|
+
total_weights = torch.ger(total_weights, w).reshape(-1) # Use outer product and flatten
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
points, weights = sampled_points, total_weights.reshape(-1) # Ensure weights are a flat array
|
|
201
|
+
points = points.to(torch.get_default_dtype())
|
|
202
|
+
weights = weights.to(torch.get_default_dtype())
|
|
203
|
+
return points, weights
|
|
204
|
+
|
|
205
|
+
def _sample_trapezoidal(self, num_points):
|
|
206
|
+
raise RuntimeError("DO NOT USE THIS method. It's more or less the same as midpoint rule....")
|
|
207
|
+
num_points = np.array(num_points)
|
|
208
|
+
if len(num_points) != self.bounds.shape[0]:
|
|
209
|
+
raise ValueError("Using trapezoidal sampling expected num_points to match the number of dimensions.")
|
|
210
|
+
|
|
211
|
+
sample_points = []
|
|
212
|
+
weights = []
|
|
213
|
+
|
|
214
|
+
for i in range(self.bounds.shape[0]):
|
|
215
|
+
lower_bound, upper_bound = self.bounds[i]
|
|
216
|
+
dx = (upper_bound - lower_bound) / (num_points[i] - 1)
|
|
217
|
+
x = torch.linspace(lower_bound, upper_bound, num_points[i])
|
|
218
|
+
sample_points.append(x)
|
|
219
|
+
|
|
220
|
+
# Weights for the trapezoidal rule
|
|
221
|
+
w = torch.ones(num_points[i])
|
|
222
|
+
w[0] /= 2 # First point weight
|
|
223
|
+
w[-1] /= 2 # Last point weight
|
|
224
|
+
weights.append(w * dx) # Multiply by dx to get the correct area contribution
|
|
225
|
+
|
|
226
|
+
# Create a meshgrid of sample points
|
|
227
|
+
grid = torch.meshgrid(*sample_points, indexing='ij')
|
|
228
|
+
sampled_points = torch.stack(grid, dim=-1).reshape(-1, self.bounds.shape[0])
|
|
229
|
+
|
|
230
|
+
# Calculate total weights
|
|
231
|
+
total_weights = weights[0]
|
|
232
|
+
for w in weights[1:]:
|
|
233
|
+
total_weights = total_weights.unsqueeze(-1) * w.unsqueeze(0) # Use broadcasting to multiply correctly
|
|
234
|
+
|
|
235
|
+
points, weights = sampled_points, total_weights.reshape(-1)
|
|
236
|
+
points = points.to(torch.get_default_dtype())
|
|
237
|
+
weights = weights.to(torch.get_default_dtype())
|
|
238
|
+
return points, weights
|
|
239
|
+
|
|
240
|
+
def _sample_monte_carlo(self, num_points):
|
|
241
|
+
num_points = check_2val(num_points)
|
|
242
|
+
if len(num_points.shape)!=0:
|
|
243
|
+
raise ValueError("num_points for monte_carlo needs to be a scalar")
|
|
244
|
+
"""Sample points uniformly using the Monte Carlo method."""
|
|
245
|
+
points = torch.empty((num_points, self.bounds.shape[0]))
|
|
246
|
+
|
|
247
|
+
for i in range(self.bounds.shape[0]):
|
|
248
|
+
# Generate random points uniformly within the bounds for each dimension
|
|
249
|
+
rand_points = mersenne_twister.uniform(0,1,size=num_points)
|
|
250
|
+
rand_points = torch.tensor(rand_points, dtype=torch.float32)
|
|
251
|
+
points[:, i] = rand_points * (self.bounds[i, 1] - self.bounds[i, 0]) + self.bounds[i, 0]
|
|
252
|
+
|
|
253
|
+
# Calculate the volume of the cube
|
|
254
|
+
volume = torch.prod(self.bounds[:,1]-self.bounds[:,0])
|
|
255
|
+
constant_multi = volume/float(num_points)
|
|
256
|
+
weights = torch.full((int(num_points),),fill_value=constant_multi)
|
|
257
|
+
|
|
258
|
+
points = points.to(torch.get_default_dtype())
|
|
259
|
+
weights = weights.to(torch.get_default_dtype())
|
|
260
|
+
return points, weights
|
|
261
|
+
|
|
262
|
+
def _sample_sobol(self, num_points,is_pow2):
|
|
263
|
+
num_points = check_2val(num_points)
|
|
264
|
+
if len(num_points.shape)!=0:
|
|
265
|
+
raise ValueError("num_points for sobol needs to be a scalar")
|
|
266
|
+
"""Sample points using the Sobol sequence method."""
|
|
267
|
+
points = None
|
|
268
|
+
num_points_log2 = np.log2(num_points)
|
|
269
|
+
if round(num_points_log2,0) != num_points_log2:
|
|
270
|
+
if is_pow2:
|
|
271
|
+
raise RuntimeError("round(num_points_log2,0) != num_points_log2"+ f",num_points_log2=={num_points_log2}")
|
|
272
|
+
|
|
273
|
+
sobol = torch.quasirandom.SobolEngine(dimension=self.bounds.shape[0], scramble=True)
|
|
274
|
+
points = sobol.draw(num_points,dtype=torch.float32)
|
|
275
|
+
else:
|
|
276
|
+
sampler = qmc.Sobol(d=self.bounds.shape[0], scramble=True)
|
|
277
|
+
points = sampler.random_base2(m=int(num_points_log2))
|
|
278
|
+
points = torch.tensor(points)
|
|
279
|
+
# Scale points according to the cube bounds
|
|
280
|
+
scaled_points = points * (self.bounds[:, 1] - self.bounds[:, 0]) + self.bounds[:, 0]
|
|
281
|
+
|
|
282
|
+
# Calculate the volume of the cube
|
|
283
|
+
volume = torch.prod(self.bounds[:,1]-self.bounds[:,0])
|
|
284
|
+
constant_multi = volume/float(num_points)
|
|
285
|
+
weights = torch.full((int(num_points),),fill_value=constant_multi)
|
|
286
|
+
|
|
287
|
+
points = points.to(torch.get_default_dtype())
|
|
288
|
+
weights = weights.to(torch.get_default_dtype())
|
|
289
|
+
return scaled_points, weights
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
class Disc(Integrator):
|
|
294
|
+
"""
|
|
295
|
+
Integrator for a 2D disc (circle).
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
radius (float): The radius of the disc.
|
|
299
|
+
|
|
300
|
+
Example:
|
|
301
|
+
>>> disc = dit.integrators.Disc(1.0)
|
|
302
|
+
>>> points, weights = disc.sample(2**4, method="sobol_pow2")
|
|
303
|
+
>>> volume = disc.get_volume()
|
|
304
|
+
>>> all_in_bounds = disc.in_bounds(points)
|
|
305
|
+
>>> print("Sampled points:", points)
|
|
306
|
+
>>> print("Integration weights:", weights)
|
|
307
|
+
>>> print("Disc area:", volume)
|
|
308
|
+
>>> print("All points in bounds:", all_in_bounds)
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
def __init__(self,radius):
|
|
312
|
+
self.radius = float(radius)
|
|
313
|
+
|
|
314
|
+
def sample(self, num_points: int | list[int], method: IntegrationMethod = IntegrationMethod.SOBOL) -> tuple[torch.Tensor, torch.Tensor]:
|
|
315
|
+
"""
|
|
316
|
+
Sample points and weights using the specified method.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
num_points (int or list): Number of points in each dimension.
|
|
320
|
+
method (str): The integration method to use. Options are 'simpson', 'midpoint', 'monte_carlo', 'sobol', and 'sobol_pow2'.
|
|
321
|
+
Returns:
|
|
322
|
+
tuple: A tuple containing the sampled points and their corresponding weights.
|
|
323
|
+
"""
|
|
324
|
+
if not isinstance(method, str):
|
|
325
|
+
method = str(method.value)
|
|
326
|
+
|
|
327
|
+
if method == 'simpson':
|
|
328
|
+
return self._sample_simpson(num_points)
|
|
329
|
+
elif method == 'monte_carlo':
|
|
330
|
+
return self._sample_monte_carlo(num_points)
|
|
331
|
+
elif method == 'sobol':
|
|
332
|
+
return self._sample_sobol(num_points,False)
|
|
333
|
+
elif method == 'sobol_pow2':
|
|
334
|
+
return self._sample_sobol(num_points,True)
|
|
335
|
+
elif method == 'midpoint':
|
|
336
|
+
return self._sample_midpoint(num_points)
|
|
337
|
+
else:
|
|
338
|
+
raise ValueError(f"Unknown integration method: {method}")
|
|
339
|
+
|
|
340
|
+
def _sample_midpoint(self, num_points):
|
|
341
|
+
#raise RuntimeError("midpoint rule not implemted for disc")
|
|
342
|
+
num_points = np.array(num_points)
|
|
343
|
+
|
|
344
|
+
midpoints = []
|
|
345
|
+
|
|
346
|
+
# Calculate the midpoints for each dimension
|
|
347
|
+
lower_bound, upper_bound = [-self.radius,self.radius]
|
|
348
|
+
for i in range(2):
|
|
349
|
+
# Calculate the size of each interval (dx) for the dimension
|
|
350
|
+
dx = (upper_bound - lower_bound) / num_points[i]
|
|
351
|
+
# Compute the midpoints in this dimension
|
|
352
|
+
points = torch.linspace(lower_bound + dx / 2.0, upper_bound - dx / 2.0, num_points[i])
|
|
353
|
+
midpoints.append(points)
|
|
354
|
+
|
|
355
|
+
# Create a meshgrid of midpoints for all dimensions
|
|
356
|
+
grid = torch.meshgrid(*midpoints, indexing='ij')
|
|
357
|
+
sampled_points = torch.stack(grid, dim=-1).reshape(-1, 2)
|
|
358
|
+
|
|
359
|
+
# Compute the weights based on the volume of each subregion
|
|
360
|
+
weights = torch.ones(sampled_points.shape[0], dtype=torch.float32)
|
|
361
|
+
|
|
362
|
+
lower_bound, upper_bound = [-self.radius,self.radius]
|
|
363
|
+
for i in range(2):
|
|
364
|
+
dx = (upper_bound - lower_bound) / num_points[i]
|
|
365
|
+
# Each dimension contributes its own weight
|
|
366
|
+
weights *= dx # Multiply the weights by the width of the intervals
|
|
367
|
+
in_bounds = self.in_bounds(sampled_points)
|
|
368
|
+
sampled_points = sampled_points[in_bounds]
|
|
369
|
+
weights = weights[in_bounds]
|
|
370
|
+
return sampled_points, weights
|
|
371
|
+
|
|
372
|
+
def _sample_simpson(self,num_points):
|
|
373
|
+
raise RuntimeError("simpson's rule not implemted for disc")
|
|
374
|
+
if len(num_points) != self.bounds.shape[0]:
|
|
375
|
+
raise ValueError("using simpson rule expected num_points to have the same number of entries as dimensions (4,3,2)")
|
|
376
|
+
|
|
377
|
+
def _sample_weights_from_unif(self,points):
|
|
378
|
+
num_points = points.shape[0]
|
|
379
|
+
volume = (torch.pi*(self.radius**2.0))
|
|
380
|
+
constant_multi = volume/float(num_points)
|
|
381
|
+
weights = torch.full((int(num_points),),fill_value=constant_multi)
|
|
382
|
+
weights = weights.to(torch.get_default_dtype())
|
|
383
|
+
return weights
|
|
384
|
+
|
|
385
|
+
def _sample_points_from_unif(self,points):
|
|
386
|
+
# Scale points to the disc
|
|
387
|
+
num_points = points.shape[0]
|
|
388
|
+
r_points = self.radius * torch.sqrt(points[:, 0]) # Use sqrt to ensure uniform distribution
|
|
389
|
+
theta = 2 * torch.pi * points[:, 1]
|
|
390
|
+
|
|
391
|
+
# Convert polar to Cartesian coordinates
|
|
392
|
+
x = r_points * torch.cos(theta)
|
|
393
|
+
y = r_points * torch.sin(theta)
|
|
394
|
+
|
|
395
|
+
# Stack x and y to get the final points
|
|
396
|
+
points = torch.stack((x, y), dim=1)
|
|
397
|
+
points = points.to(torch.get_default_dtype())
|
|
398
|
+
return points
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _sample_monte_carlo(self, num_points):
|
|
402
|
+
num_points = check_2val(num_points)
|
|
403
|
+
#points = torch.rand(num_points,2)
|
|
404
|
+
rand_points = mersenne_twister.uniform(0,1,size=num_points*2)
|
|
405
|
+
rand_points = torch.tensor(rand_points, dtype=torch.float32)
|
|
406
|
+
rand_points = rand_points.reshape(num_points,2)
|
|
407
|
+
|
|
408
|
+
out_points = self._sample_points_from_unif(rand_points)
|
|
409
|
+
out_weights = self._sample_weights_from_unif(rand_points)
|
|
410
|
+
return out_points,out_weights
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _sample_sobol(self, num_points,is_pow2):
|
|
414
|
+
num_points = check_2val(num_points)
|
|
415
|
+
|
|
416
|
+
points = None
|
|
417
|
+
num_points_log2 = np.log2(num_points)
|
|
418
|
+
if round(num_points_log2,0) != num_points_log2:
|
|
419
|
+
if is_pow2:
|
|
420
|
+
raise RuntimeError("round(num_points_log2,0) != num_points_log2"+ f",num_points_log2=={num_points_log2}")
|
|
421
|
+
sobol = torch.quasirandom.SobolEngine(dimension=2, scramble=True)
|
|
422
|
+
points = sobol.draw(num_points,dtype=torch.float32)
|
|
423
|
+
else:
|
|
424
|
+
sampler = qmc.Sobol(d=2, scramble=True)
|
|
425
|
+
points = sampler.random_base2(m=int(num_points_log2))
|
|
426
|
+
points = torch.tensor(points)
|
|
427
|
+
|
|
428
|
+
out_points = self._sample_points_from_unif(points)
|
|
429
|
+
out_weights = self._sample_weights_from_unif(points)
|
|
430
|
+
return out_points,out_weights
|
|
431
|
+
|
|
432
|
+
def in_bounds(self,x):
|
|
433
|
+
"""Check if points are within the disc.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
x (torch.Tensor): Points to check.
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
torch.Tensor: Boolean tensor indicating if points are within the disc.
|
|
440
|
+
"""
|
|
441
|
+
device = x.device
|
|
442
|
+
dtype = x.dtype
|
|
443
|
+
return torch.linalg.norm(x,dim=1)<self.radius
|
|
444
|
+
|
|
445
|
+
def get_volume(self):
|
|
446
|
+
"""Calculate the volume of the disc.
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
float: Volume of the disc.
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
return math.pi*self.radius**2.
|