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.
- lsdo_function_spaces/__init__.py +64 -0
- lsdo_function_spaces/core/__init__.py +0 -0
- lsdo_function_spaces/core/function.py +1322 -0
- lsdo_function_spaces/core/function_set.py +1081 -0
- lsdo_function_spaces/core/function_set_space.py +379 -0
- lsdo_function_spaces/core/function_space.py +482 -0
- lsdo_function_spaces/core/operations/__init__.py +0 -0
- lsdo_function_spaces/core/operations/basic_ops.py +85 -0
- lsdo_function_spaces/core/operations/operations.py +5 -0
- lsdo_function_spaces/core/optimization.py +183 -0
- lsdo_function_spaces/core/spaces/__init__.py +0 -0
- lsdo_function_spaces/core/spaces/b_spline_space.py +418 -0
- lsdo_function_spaces/core/spaces/conditional_space.py +65 -0
- lsdo_function_spaces/core/spaces/constant_space.py +57 -0
- lsdo_function_spaces/core/spaces/idw_space.py +271 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/__init__.py +0 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_csdl_custom_ops.py +420 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_patch_projection.py +1022 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_patch_projection_non_differentiable.py +186 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_patch_projection_optimized.py +594 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/b_spline_space_new.py +6 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_jax.py +172 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_jax_factory.py +382 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_jax_stencil.py +451 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_numpy.py +249 -0
- lsdo_function_spaces/core/spaces/non_cython_bsplines/compute_basis_matrix_numpy_factory.py +391 -0
- lsdo_function_spaces/core/spaces/operation_space.py +64 -0
- lsdo_function_spaces/core/spaces/polynomial_space.py +79 -0
- lsdo_function_spaces/core/spaces/rbf_space.py +136 -0
- lsdo_function_spaces/core/spaces/tri_space.py +256 -0
- lsdo_function_spaces/utils/__init__.py +0 -0
- lsdo_function_spaces/utils/file_io.py +484 -0
- lsdo_function_spaces/utils/internal_utilities.py +11 -0
- lsdo_function_spaces/utils/plotting_functions.py +357 -0
- lsdo_function_spaces/utils/utility_functions.py +148 -0
- lsdo_function_spaces-1.0.0.dist-info/METADATA +189 -0
- lsdo_function_spaces-1.0.0.dist-info/RECORD +40 -0
- lsdo_function_spaces-1.0.0.dist-info/WHEEL +5 -0
- lsdo_function_spaces-1.0.0.dist-info/licenses/LICENSE.txt +165 -0
- 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
|