lsdo-function-spaces 1.0.0__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 (40) hide show
  1. lsdo_function_spaces/__init__.py +64 -0
  2. lsdo_function_spaces/core/__init__.py +0 -0
  3. lsdo_function_spaces/core/function.py +1322 -0
  4. lsdo_function_spaces/core/function_set.py +1081 -0
  5. lsdo_function_spaces/core/function_set_space.py +379 -0
  6. lsdo_function_spaces/core/function_space.py +482 -0
  7. lsdo_function_spaces/core/operations/__init__.py +0 -0
  8. lsdo_function_spaces/core/operations/basic_ops.py +85 -0
  9. lsdo_function_spaces/core/operations/operations.py +5 -0
  10. lsdo_function_spaces/core/optimization.py +183 -0
  11. lsdo_function_spaces/core/spaces/__init__.py +0 -0
  12. lsdo_function_spaces/core/spaces/b_spline_space.py +418 -0
  13. lsdo_function_spaces/core/spaces/conditional_space.py +65 -0
  14. lsdo_function_spaces/core/spaces/constant_space.py +57 -0
  15. lsdo_function_spaces/core/spaces/idw_space.py +271 -0
  16. lsdo_function_spaces/core/spaces/non_cython_bsplines/__init__.py +0 -0
  17. lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_csdl_custom_ops.py +420 -0
  18. lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_patch_projection.py +1022 -0
  19. lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_patch_projection_non_differentiable.py +186 -0
  20. lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_patch_projection_optimized.py +594 -0
  21. lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_space_new.py +6 -0
  22. lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_jax.py +172 -0
  23. lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_jax_factory.py +382 -0
  24. lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_jax_stencil.py +451 -0
  25. lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_numpy.py +249 -0
  26. lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_numpy_factory.py +391 -0
  27. lsdo_function_spaces/core/spaces/operation_space.py +64 -0
  28. lsdo_function_spaces/core/spaces/polynomial_space.py +79 -0
  29. lsdo_function_spaces/core/spaces/rbf_space.py +136 -0
  30. lsdo_function_spaces/core/spaces/tri_space.py +256 -0
  31. lsdo_function_spaces/utils/__init__.py +0 -0
  32. lsdo_function_spaces/utils/file_io.py +484 -0
  33. lsdo_function_spaces/utils/internal_utilities.py +11 -0
  34. lsdo_function_spaces/utils/plotting_functions.py +357 -0
  35. lsdo_function_spaces/utils/utility_functions.py +148 -0
  36. lsdo_function_spaces-1.0.0.dist-info/METADATA +189 -0
  37. lsdo_function_spaces-1.0.0.dist-info/RECORD +40 -0
  38. lsdo_function_spaces-1.0.0.dist-info/WHEEL +5 -0
  39. lsdo_function_spaces-1.0.0.dist-info/licenses/LICENSE.txt +165 -0
  40. lsdo_function_spaces-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,256 @@
1
+ import numpy as np
2
+ import scipy.sparse as sps
3
+ from ..function_space import LinearFunctionSpace
4
+ from scipy.spatial.distance import cdist
5
+ from dataclasses import dataclass
6
+ from typing import Union
7
+ import csdl_alpha as csdl
8
+
9
+ class LinearTriangulationSpace(LinearFunctionSpace):
10
+ def __init__(self, nodes=None, elements=None, grid_size=(10,10)):
11
+ """
12
+ Triangulation Function Space.
13
+
14
+ This function space represents a mesh of 3-node triangles
15
+
16
+ Parameters
17
+ ----------
18
+ nodes : np.ndarray
19
+ The nodes of the mesh. (n_nodes, 2)
20
+ elements : np.ndarray
21
+ The elements of the mesh. (n_elements, 3)
22
+ """
23
+ if nodes is None:
24
+ # Create a grid of nodes based on the grid size
25
+ x = np.linspace(0, 1, grid_size[0])
26
+ y = np.linspace(0, 1, grid_size[1])
27
+ nodes = np.array(np.meshgrid(x, y)).T.reshape(-1, 2)
28
+
29
+ # create elements
30
+ elements = []
31
+ for i in range(grid_size[0]-1):
32
+ for j in range(grid_size[1]-1):
33
+ elements.append([(i+1)*grid_size[0]+j+1, (i+1)*grid_size[0]+j, i*grid_size[0]+j+1])
34
+ elements.append([i*grid_size[0]+j, i*grid_size[0]+j+1, (i+1)*grid_size[0]+j])
35
+
36
+ elements = np.array(elements)
37
+
38
+ num_parametric_dimensions = 2
39
+ self.nodes = nodes
40
+ self.elements = elements
41
+
42
+ super().__init__(num_parametric_dimensions, (self.nodes.shape[0],))
43
+
44
+ def compute_basis_matrix(self, parametric_coordinates:np.ndarray, parametric_derivative_orders: np.ndarray=None, expansion_factor:int=None) -> np.ndarray:
45
+ """
46
+ Compute the basis matrix for the given parametric coordinates.
47
+
48
+ Parameters
49
+ ----------
50
+ parametric_coordinates : np.ndarray
51
+ The parametric coordinates for which to compute the basis matrix.
52
+ parametric_derivative_orders : np.ndarray, optional
53
+ The derivative orders of the parametric coordinates. Default is None.
54
+ expansion_factor : int, optional
55
+ The expansion factor. Default is None.
56
+
57
+ Returns
58
+ -------
59
+ np.ndarray
60
+ The computed basis matrix.
61
+
62
+ Raises
63
+ ------
64
+ NotImplementedError
65
+ If parametric_derivative_orders or expansion_factor is not None.
66
+
67
+ """
68
+ if expansion_factor is not None:
69
+ raise NotImplementedError
70
+
71
+ # Compute what element each parametric coordinate is in and the local coordinates within that element
72
+ elemental_indices, elemental_coordinates = self.compute_elemental_coordinates(parametric_coordinates)
73
+
74
+ # Compute basis matrix
75
+ basis_matrix = np.zeros((parametric_coordinates.shape[0], self.nodes.shape[0]))
76
+ pdo = tuple(parametric_derivative_orders) if parametric_derivative_orders is not None else None
77
+
78
+ if pdo is None or pdo == (0, 0):
79
+ submatrices = self.compute_shape_functions(elemental_coordinates)
80
+ else:
81
+ submatrices = self.compute_shape_function_gradients(elemental_indices, elemental_coordinates, pdo)
82
+
83
+ for i, element_index in enumerate(elemental_indices):
84
+ if submatrices is not None:
85
+ element_coord_indices = self.elements[element_index]
86
+ basis_matrix[i, element_coord_indices] = submatrices[i]
87
+ else:
88
+ break
89
+
90
+ return basis_matrix
91
+
92
+ def compute_elemental_coordinates(self, parametric_coordinates):
93
+ """
94
+ Compute the element index and local coordinates within the element for the given parametric coordinates.
95
+
96
+ Parameters
97
+ ----------
98
+ parametric_coordinates : np.ndarray
99
+ The parametric coordinates for which to compute the element index and local coordinates.
100
+
101
+ Returns
102
+ -------
103
+ np.ndarray
104
+ The element index for each parametric coordinate.
105
+ np.ndarray
106
+ The local coordinates within the element for each parametric coordinate.
107
+
108
+ """
109
+ # Compute the element local coordinates
110
+ local_coordinates = np.zeros((len(self.elements), parametric_coordinates.shape[0], 2))
111
+ shape_functions = np.zeros((len(self.elements), parametric_coordinates.shape[0], 3))
112
+ for i, element in enumerate(self.elements):
113
+ local_coordinates[i,:,:] = self.compute_local_coordinates(parametric_coordinates, self.nodes[element])
114
+ shape_functions[i,:,:] = self.compute_shape_functions(local_coordinates[i,:,:])
115
+
116
+ # Find the element each parametric coordinate is in
117
+ tol = 1e-10 # TODO: consider only applying the tol if no element is found
118
+ elemental_indices = np.all((shape_functions <= 1+tol) & (shape_functions >= 0-tol), axis=2).argmax(axis=0)
119
+
120
+ # Compute the local coordinates within the element
121
+ elemental_coordinates = local_coordinates[elemental_indices, np.arange(parametric_coordinates.shape[0])]
122
+
123
+ return elemental_indices, elemental_coordinates
124
+
125
+ def compute_local_coordinates(self, parametric_coordinates, element_nodes):
126
+ """
127
+ Computes the local coordinates within the element for the given parametric coordinates.
128
+
129
+ Parameters
130
+ ----------
131
+ parametric_coordinates : np.ndarray
132
+ The parametric coordinates for which to compute the local coordinates.
133
+
134
+ Returns
135
+ -------
136
+ np.ndarray
137
+ The local coordinates within the element for each parametric coordinate. (n_parametric_coordinates, 3)
138
+ """
139
+ x1, x2, x3 = element_nodes[0,0], element_nodes[1,0], element_nodes[2,0]
140
+ y1, y2, y3 = element_nodes[0,1], element_nodes[1,1], element_nodes[2,1]
141
+ x, y = parametric_coordinates[:,0], parametric_coordinates[:,1]
142
+
143
+ area2 = x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2)
144
+
145
+ xi = ((x-x1)*(y3-y1)-(y-y1)*(x3-x1))/area2
146
+ eta = ((x1-x1)*(y-y1)-(y2-y1)*(x-x1))/area2
147
+
148
+ return np.vstack((xi, eta)).T
149
+
150
+ def compute_shape_functions(self, local_coordinates):
151
+ """
152
+ Compute the shape functions for the given local coordinates.
153
+
154
+ Parameters
155
+ ----------
156
+ local_coordinates : np.ndarray
157
+ The local coordinates for which to compute the shape functions.
158
+
159
+ Returns
160
+ -------
161
+ np.ndarray
162
+ The computed shape functions.
163
+ """
164
+
165
+ N1 = 1-local_coordinates[:,0]-local_coordinates[:,1]
166
+ N2 = local_coordinates[:,0]
167
+ N3 = local_coordinates[:,1]
168
+
169
+ return np.vstack((N1, N2, N3)).T
170
+
171
+ def compute_shape_function_gradients(self, elemental_indices, local_coordinates, pdo):
172
+ """
173
+ Compute the shape function gradients for the given local coordinates.
174
+
175
+ Parameters
176
+ ----------
177
+ elemental_indices : np.ndarray
178
+ The element indices for which to compute the shape function gradients.
179
+ local_coordinates : np.ndarray
180
+ The local coordinates for which to compute the shape function gradients.
181
+ pdo : tuple
182
+ parametric_derivative_orders
183
+
184
+ Returns
185
+ -------
186
+ np.ndarray
187
+ The computed shape function gradients.
188
+ """
189
+ gradient = np.zeros((elemental_indices.shape[0], 3))
190
+ if pdo == (1, 0) or pdo == (0, 1):
191
+ for i, element_index in enumerate(elemental_indices):
192
+ element_nodes = self.nodes[self.elements[element_index]]
193
+ x1, x2, x3 = element_nodes[0,0], element_nodes[1,0], element_nodes[2,0]
194
+ y1, y2, y3 = element_nodes[0,1], element_nodes[1,1], element_nodes[2,1]
195
+
196
+ area2 = x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2)
197
+
198
+ if pdo == (1, 0):
199
+ gradient[i] = np.array([y2-y3, y3-y1, y1-y2])/area2
200
+ else:
201
+ gradient[i] = np.array([x3-x2, x1-x3, x2-x1])/area2
202
+
203
+ return gradient
204
+
205
+ def _compute_distance_bounds(self, point:np.ndarray, function, direction=None) -> float:
206
+ '''
207
+ Computes the distance bounds for the given point.
208
+ '''
209
+ if not hasattr(function, 'bounding_box'):
210
+ coefficients = function.coefficients.value.reshape((-1, function.num_physical_dimensions))
211
+ function.bounding_box = np.zeros((2, coefficients.shape[-1]))
212
+ if self.num_parametric_dimensions == 1:
213
+ function.bounding_box[0, 0] = np.min(coefficients)
214
+ function.bounding_box[1, 0] = np.max(coefficients)
215
+ else:
216
+ function.bounding_box[0, :] = np.min(coefficients, axis=0)
217
+ function.bounding_box[1, :] = np.max(coefficients, axis=0)
218
+
219
+ if direction is None:
220
+ neg = function.bounding_box[0] - point
221
+ pos = point - function.bounding_box[1]
222
+ distance_vector = np.maximum(np.maximum(neg, pos), 0)
223
+ return np.linalg.norm(distance_vector)
224
+ else:
225
+ closest_point = np.zeros((len(point),))
226
+ for i in range(len(point)):
227
+ if point[i] < function.bounding_box[0, i]:
228
+ closest_point[i] = function.bounding_box[0, i]
229
+ elif point[i] > function.bounding_box[1, i]:
230
+ closest_point[i] = function.bounding_box[1, i]
231
+ else:
232
+ closest_point[i] = point[i]
233
+ t = np.dot(direction, (closest_point - point)) / np.dot(direction, direction)
234
+ closest_point_on_line = point + t * direction
235
+ return np.linalg.norm(closest_point_on_line - closest_point)
236
+
237
+ def test_tri():
238
+ from scipy.stats.qmc import LatinHypercube
239
+ import lsdo_function_spaces as lfs
240
+
241
+ rec = csdl.Recorder(inline=True)
242
+ rec.start()
243
+
244
+ space = lfs.LinearTriangulationSpace(grid_size=(10,10))
245
+
246
+ np.random.seed(0)
247
+ num_points = 1000
248
+ rand_parametric_coordinates = LatinHypercube(d=2, seed=7).random(num_points)
249
+
250
+ parametric_coordinates = space.nodes
251
+ height = (np.sin(2*np.pi*parametric_coordinates[:,0]) + .5*np.cos(2*np.pi*parametric_coordinates[:,1])).reshape(-1,1)
252
+ data = np.hstack((parametric_coordinates*10, height))
253
+
254
+ function = lfs.Function(space, data)
255
+ function.evaluate(rand_parametric_coordinates)
256
+
File without changes