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,482 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import csdl_alpha as csdl
|
|
3
|
+
import numpy as np
|
|
4
|
+
import numpy.typing as npt
|
|
5
|
+
import scipy.sparse as sps
|
|
6
|
+
import scipy.sparse.linalg as spsl
|
|
7
|
+
import lsdo_function_spaces as lfs
|
|
8
|
+
from typing import Union
|
|
9
|
+
from .optimization import Optimization, NewtonOptimizer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
'''
|
|
13
|
+
NOTE: To implement a function space (for instance, B-splines), all that must be implemented is the evaluation of the basis functions
|
|
14
|
+
and assembly into a basis matrix.
|
|
15
|
+
'''
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FunctionSpace:
|
|
19
|
+
'''
|
|
20
|
+
Base class for function spaces. This class is used to evaluate functions at given coordinates, refit functions, and project points onto functions.
|
|
21
|
+
'''
|
|
22
|
+
def __init__(self, num_parametric_dimensions:int, coefficients_shape:tuple):
|
|
23
|
+
"""Base class for function spaces. This class is used to evaluate functions at given coordinates, refit functions, and project points onto functions.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
num_parametric_dimensions : int
|
|
28
|
+
Number of parametric dimensions of the function space.
|
|
29
|
+
coefficients_shape : tuple
|
|
30
|
+
Shape of the coefficients of the function space (will end up flattened).
|
|
31
|
+
"""
|
|
32
|
+
# num_physical_dimensions : int # I might need this, but not using for now so don't have if not needed
|
|
33
|
+
# coefficients_shape : tuple # Seems overly restrictive making things like transition elements and other unstructured functions impossible
|
|
34
|
+
# -- It seems like we really only need the num_parametric_dimensions
|
|
35
|
+
self.num_parametric_dimensions = num_parametric_dimensions # This is really useful for the function methods
|
|
36
|
+
self.coefficients_shape = coefficients_shape
|
|
37
|
+
|
|
38
|
+
if isinstance(self.coefficients_shape, int):
|
|
39
|
+
self.coefficients_shape = (self.coefficients_shape,)
|
|
40
|
+
|
|
41
|
+
# if len(self.coefficients_shape) == 1:
|
|
42
|
+
# self.coefficients_shape = self.coefficients_shape*self.num_parametric_dimensions
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def generate_parametric_grid(self, grid_resolution:tuple) -> np.ndarray:
|
|
46
|
+
'''
|
|
47
|
+
Generates a parametric grid with the given resolution.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
grid_resolution : tuple -- shape=(num_parametric_dimensions,)
|
|
52
|
+
The resolution of the grid.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
np.ndarray -- shape=(num_points, num_parametric_dimensions)
|
|
57
|
+
The parametric grid.
|
|
58
|
+
'''
|
|
59
|
+
if isinstance(grid_resolution, int):
|
|
60
|
+
grid_resolution = (grid_resolution,)*self.num_parametric_dimensions
|
|
61
|
+
if len(grid_resolution) == 1 and self.num_parametric_dimensions > 1:
|
|
62
|
+
grid_resolution = grid_resolution * self.num_parametric_dimensions
|
|
63
|
+
|
|
64
|
+
mesh_grid_input = []
|
|
65
|
+
for dimension_index in range(self.num_parametric_dimensions):
|
|
66
|
+
mesh_grid_input.append(np.linspace(0., 1., grid_resolution[dimension_index]))
|
|
67
|
+
|
|
68
|
+
parametric_coordinates_tuple = list(np.meshgrid(*mesh_grid_input, indexing='ij'))
|
|
69
|
+
for dimensions_index in range(self.num_parametric_dimensions):
|
|
70
|
+
parametric_coordinates_tuple[dimensions_index] = parametric_coordinates_tuple[dimensions_index].reshape((-1,1))
|
|
71
|
+
|
|
72
|
+
parametric_coordinates = np.stack(parametric_coordinates_tuple, axis=-1).reshape(-1, self.num_parametric_dimensions)
|
|
73
|
+
|
|
74
|
+
return parametric_coordinates
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# def evaluate(self, coefficients:csdl.Variable, parametric_coordinates:np.ndarray, parametric_derivative_order:tuple=None,
|
|
78
|
+
# plot:bool=False) -> csdl.Variable:
|
|
79
|
+
# '''
|
|
80
|
+
# Picks a function from the function space with the given coefficients and evaluates or its derivative(s) it at the parametric coordinates.
|
|
81
|
+
|
|
82
|
+
# Parameters
|
|
83
|
+
# ----------
|
|
84
|
+
# coefficients : csdl.Variable -- shape=coefficients_shape or (num_coefficients,)
|
|
85
|
+
# The coefficients of the function.
|
|
86
|
+
# parametric_coordinates : np.ndarray -- shape=(num_points, num_parametric_dimensions)
|
|
87
|
+
# The coordinates at which to evaluate the function.
|
|
88
|
+
# parametric_derivative_order : tuple = None -- shape=(num_points, num_parametric_dimensions)
|
|
89
|
+
# The order of the parametric derivatives to evaluate.
|
|
90
|
+
# plot : bool = False
|
|
91
|
+
# Whether or not to plot the function with the points from the result of the evaluation.
|
|
92
|
+
|
|
93
|
+
# Returns
|
|
94
|
+
# -------
|
|
95
|
+
# csdl.Variable
|
|
96
|
+
# The function evaluated at the given coordinates.
|
|
97
|
+
# '''
|
|
98
|
+
# basis_matrix = self.compute_basis_matrix(parametric_coordinates, parametric_derivative_order)
|
|
99
|
+
# if isinstance(coefficients, csdl.Variable) and sps.issparse(basis_matrix):
|
|
100
|
+
# coefficients_reshaped = coefficients.reshape((basis_matrix.shape[1], coefficients.size//basis_matrix.shape[1]))
|
|
101
|
+
# # NOTE: TEMPORARY IMPLEMENTATION SINCE CSDL ONLY SUPPORTS SPARSE MATVECS AND NOT MATMATS
|
|
102
|
+
# values = csdl.Variable(value=np.zeros((basis_matrix.shape[0], coefficients_reshaped.shape[1])))
|
|
103
|
+
# for i in range(coefficients_reshaped.shape[1]):
|
|
104
|
+
# coefficients_column = coefficients_reshaped[:,i].reshape((coefficients_reshaped.shape[0],1))
|
|
105
|
+
# values = values.set(csdl.slice[:,i], csdl.sparse.matvec(basis_matrix, coefficients_column).reshape((basis_matrix.shape[0],)))
|
|
106
|
+
# # values = csdl.sparse.matvec or matmat(basis_matrix, coefficients_reshaped)
|
|
107
|
+
# elif isinstance(coefficients, csdl.Variable):
|
|
108
|
+
# values = csdl.matvec(basis_matrix, coefficients)
|
|
109
|
+
# else:
|
|
110
|
+
# values = basis_matrix.dot(coefficients.reshape((basis_matrix.shape[1], -1)))
|
|
111
|
+
|
|
112
|
+
# return values
|
|
113
|
+
# # raise NotImplementedError(f"Evaluate method must be implemented in {type(self)} class.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def compute_basis_matrix(self, parametric_coordinates:np.ndarray, parametric_derivative_orders:np.ndarray=None) -> sps.csc_matrix:
|
|
117
|
+
'''
|
|
118
|
+
Evaluates the basis functions in parametric space and assembles the basis matrix (B(u) in P=B(u).dot(C)) where
|
|
119
|
+
B(u) is the evaluation matrix, P are the evaluated points in physical space, and C is the coefficients.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
parametric_coordinates : np.ndarray -- shape=(num_points, num_parametric_dimensions)
|
|
124
|
+
The coordinates at which to evaluate the function.
|
|
125
|
+
parametric_derivative_orders : np.ndarray = None -- shape=(num_points, num_parametric_dimensions)
|
|
126
|
+
The derivative orders to evaluate.
|
|
127
|
+
|
|
128
|
+
Returns
|
|
129
|
+
-------
|
|
130
|
+
sps.csc_matrix
|
|
131
|
+
The evaluation matrix.
|
|
132
|
+
'''
|
|
133
|
+
raise NotImplementedError(f"Compute evaluation matrix method must be implemented in {type(self)} class.")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def compute_fitting_map(self, parametric_coordinates):
|
|
137
|
+
raise NotImplementedError(f"Compute fitting map method must be implemented in {type(self)} class.")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# def refit(self, coefficients:csdl.Variable, grid_resolution:tuple=None, parametric_coordinates:np.ndarray=None,
|
|
141
|
+
# parametric_derivative_orders:np.ndarray=None, regularization_parameter:float=None) -> csdl.Variable:
|
|
142
|
+
# '''
|
|
143
|
+
# Picks a function from the function space with the given coefficients and evaluates or its derivative(s) it at the parametric coordinates.
|
|
144
|
+
# It then uses these values to refit the function. Either a grid resolution or parametric coordinates must be provided.
|
|
145
|
+
# If both are provided, the parametric coordinates will be used. If derivatives are used, the parametric derivative orders must be provided.
|
|
146
|
+
|
|
147
|
+
# Parameters
|
|
148
|
+
# ----------
|
|
149
|
+
# coefficients : csdl.Variable -- shape=coefficients_shape
|
|
150
|
+
# The coefficients of the function to refit.
|
|
151
|
+
# grid_resolution : tuple = None -- shape=(num_parametric_dimensions,)
|
|
152
|
+
# The grid resolution to use for refitting.
|
|
153
|
+
# parametric_coordinates : np.ndarray = None -- shape=(num_points, num_parametric_dimensions)
|
|
154
|
+
# The parametric coordinates to use for refitting.
|
|
155
|
+
# parametric_derivative_orders : np.ndarray = None -- shape=(num_points, num_parametric_dimensions)
|
|
156
|
+
# The derivative orders to use for refitting.
|
|
157
|
+
# regularization_parameter : float = None
|
|
158
|
+
# The regularization parameter to use for refitting. If None, no regularization is used.
|
|
159
|
+
|
|
160
|
+
# Returns
|
|
161
|
+
# -------
|
|
162
|
+
# csdl.Variable
|
|
163
|
+
# The refitted coefficients.
|
|
164
|
+
# '''
|
|
165
|
+
|
|
166
|
+
# '''
|
|
167
|
+
# NOTE: TODO: Look at error in L2 sense and think about whether this actually minimizes the error!!
|
|
168
|
+
# Additional NOTE: When the order changes, the ideal parametric coordinate corresponding to a value seems like it might change.
|
|
169
|
+
# -- To clarify: A point that is at u=0.1 in one function space may actually ideally be at u=0.15 or whatever in another function space.
|
|
170
|
+
# '''
|
|
171
|
+
# if parametric_coordinates is None and grid_resolution is None:
|
|
172
|
+
# raise ValueError("Either grid resolution or parametric coordinates must be provided.")
|
|
173
|
+
# if parametric_coordinates is not None and grid_resolution is not None:
|
|
174
|
+
# print("Warning: Both grid resolution and parametric coordinates were provided. Using parametric coordinates.")
|
|
175
|
+
# # raise Warning("Both grid resolution and parametric coordinates were provided. Using parametric coordinates.")
|
|
176
|
+
|
|
177
|
+
# if parametric_coordinates is None:
|
|
178
|
+
# # if grid_resolution is not None: # Don't need this line because we already error checked at the beginning.
|
|
179
|
+
# mesh_grid_input = []
|
|
180
|
+
# for dimension_index in range(grid_resolution.shape[0]): # Grid resolution is a tuple of the number of points in each parametric dimension
|
|
181
|
+
# mesh_grid_input.append(np.linspace(0., 1., grid_resolution[dimension_index]))
|
|
182
|
+
|
|
183
|
+
# parametric_coordinates_tuple = np.meshgrid(*mesh_grid_input, indexing='ij')
|
|
184
|
+
# for dimensions_index in range(grid_resolution.shape[0]):
|
|
185
|
+
# parametric_coordinates_tuple[dimensions_index] = parametric_coordinates_tuple[dimensions_index].reshape((-1,1))
|
|
186
|
+
|
|
187
|
+
# parametric_coordinates = np.hstack(parametric_coordinates_tuple)
|
|
188
|
+
|
|
189
|
+
# basis_matrix = self.compute_basis_matrix(parametric_coordinates, parametric_derivative_orders)
|
|
190
|
+
# fitting_values = basis_matrix.dot(coefficients)
|
|
191
|
+
|
|
192
|
+
# coefficients = self.fit(values=fitting_values, basis_matrix=basis_matrix, regularization_parameter=regularization_parameter)
|
|
193
|
+
|
|
194
|
+
# return coefficients
|
|
195
|
+
|
|
196
|
+
# # raise NotImplementedError(f"Refit method must be implemented in {type(self)} class.")
|
|
197
|
+
# # NOTE: This doesn't just call fit so we don't need to construct the evaluation matrix multiplie times.
|
|
198
|
+
# # - Maybe it would be easier to have the fit function optionally take in the evaluation matrix?
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def fit(self, values:Union[csdl.Variable, np.ndarray], parametric_coordinates:np.ndarray, parametric_derivative_orders:np.ndarray=None,
|
|
203
|
+
regularization_parameter:float=None, constraint:callable=None) -> csdl.Variable:
|
|
204
|
+
'''
|
|
205
|
+
Fits the function to the given data. If derivatives are used, the parametric derivative orders must be provided.
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
values : csdl.Variable|np.ndarray -- shape=(num_points,num_physical_dimensions)
|
|
210
|
+
The values of the data.
|
|
211
|
+
parametric_coordinates : np.ndarray -- shape=(num_points, num_parametric_dimensions)
|
|
212
|
+
The parametric coordinates of the data.
|
|
213
|
+
parametric_derivative_orders : np.ndarray = None -- shape=(num_points, num_parametric_dimensions)
|
|
214
|
+
The derivative orders to fit.
|
|
215
|
+
regularization_parameter : float = None
|
|
216
|
+
The regularization parameter to use for fitting. If None, no regularization is used.
|
|
217
|
+
constraint : callable = None
|
|
218
|
+
The constraint to use for fitting. Takes in a function and returns the constraint residual.
|
|
219
|
+
Function signature: constraint(function) -> csdl.Variable | list[csdl.Variable]
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
csdl.Variable
|
|
224
|
+
The coefficients of the fitted function.
|
|
225
|
+
'''
|
|
226
|
+
# General nonlinear fit via optimization
|
|
227
|
+
|
|
228
|
+
# initialize coefficients
|
|
229
|
+
coefficients = csdl.Variable(value=np.zeros(self.coefficients_shape + (values.shape[-1],)))
|
|
230
|
+
|
|
231
|
+
# compute constraints
|
|
232
|
+
if constraint is not None:
|
|
233
|
+
test_function = lfs.Function(space=self, coefficients=coefficients)
|
|
234
|
+
constraint_res = constraint(test_function) # TODO: name?
|
|
235
|
+
else:
|
|
236
|
+
constraint_res = None
|
|
237
|
+
|
|
238
|
+
# compute residual
|
|
239
|
+
test_values = self._evaluate(coefficients, parametric_coordinates, parametric_derivative_orders)
|
|
240
|
+
if test_values.shape != values.shape:
|
|
241
|
+
test_values = test_values.reshape(values.shape)
|
|
242
|
+
if regularization_parameter is None:
|
|
243
|
+
residual = csdl.sum((test_values - values)**2)
|
|
244
|
+
else:
|
|
245
|
+
residual = csdl.sum((test_values - values)**2) + regularization_parameter * csdl.sum(coefficients**2)
|
|
246
|
+
|
|
247
|
+
# create and run optimization
|
|
248
|
+
optimizer = NewtonOptimizer()
|
|
249
|
+
optimization = Optimization()
|
|
250
|
+
optimization.add_objective(residual)
|
|
251
|
+
optimization.add_design_variable(coefficients)
|
|
252
|
+
if constraint_res is not None:
|
|
253
|
+
optimization.add_constraint(constraint_res)
|
|
254
|
+
|
|
255
|
+
optimizer.add_optimization(optimization)
|
|
256
|
+
optimizer.run()
|
|
257
|
+
|
|
258
|
+
return coefficients
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def fit_function(self, values:np.ndarray, parametric_coordinates:np.ndarray, parametric_derivative_orders:np.ndarray=None,
|
|
262
|
+
regularization_parameter:float=None, constraint:callable=None) -> lfs.Function:
|
|
263
|
+
'''
|
|
264
|
+
Fits the function to the given data. Either parametric coordinates or an evaluation matrix must be provided. If derivatives are used, the
|
|
265
|
+
parametric derivative orders must be provided. If both parametric coordinates and an evaluation matrix are provided, the evaluation matrix
|
|
266
|
+
will be used.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
values : csdl.Variable|np.ndarray -- shape=(num_points,num_physical_dimensions)
|
|
271
|
+
The values of the data.
|
|
272
|
+
parametric_coordinates : np.ndarray -- shape=(num_points, num_parametric_dimensions)
|
|
273
|
+
The parametric coordinates of the data.
|
|
274
|
+
parametric_derivative_orders : np.ndarray = None -- shape=(num_points, num_parametric_dimensions)
|
|
275
|
+
The derivative orders to fit.
|
|
276
|
+
regularization_parameter : float = None
|
|
277
|
+
The regularization parameter to use for fitting. If None, no regularization is used.
|
|
278
|
+
constraint : callable = None
|
|
279
|
+
The constraint to use for fitting. Takes in a function and returns the constraint residual.
|
|
280
|
+
Function signature: constraint(function) -> csdl.Variable | list[csdl.Variable]
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
lfs.Function
|
|
285
|
+
'''
|
|
286
|
+
coefficients = self.fit(values=values, parametric_coordinates=parametric_coordinates, parametric_derivative_orders=parametric_derivative_orders,
|
|
287
|
+
regularization_parameter=regularization_parameter, constraint=constraint)
|
|
288
|
+
function = lfs.Function(space=self, coefficients=coefficients)
|
|
289
|
+
return function
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _compute_distance_bounds(self, point, function, direction=None):
|
|
293
|
+
raise NotImplementedError(f"Compute distance bounds method must be implemented in {type(self)} class.")
|
|
294
|
+
|
|
295
|
+
def _generate_projection_grid_search_resolution(self, grid_search_density_parameter):
|
|
296
|
+
grid_search_resolution = []
|
|
297
|
+
for dimension_length in self.coefficients_shape:
|
|
298
|
+
grid_search_resolution.append(int(dimension_length*grid_search_density_parameter))
|
|
299
|
+
return tuple(grid_search_resolution)
|
|
300
|
+
pass # NOTE: Don't want this to throw an error because thetr is a default is built in to the projection method.
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# NOTE: Do I want a plot function on the space? I would also have to pass in the coefficients to plot the function. What's the point?
|
|
304
|
+
# Additional NOTE: Type hinting leads to cyclic imports this way. I could just not type hint, but that's not ideal.
|
|
305
|
+
# def plot(self, point_types:list=['evaluated_points'], plot_types:list=['surface'],
|
|
306
|
+
# opacity:float=1., color:Union[str,Function]='#00629B', surface_texture:str="", additional_plotting_elements:list=[], show:bool=True):
|
|
307
|
+
# '''
|
|
308
|
+
# Plots the B-spline Surface.
|
|
309
|
+
|
|
310
|
+
# Parameters
|
|
311
|
+
# -----------
|
|
312
|
+
# points_type : list
|
|
313
|
+
# The type of points to be plotted. {evaluated_points, coefficients}
|
|
314
|
+
# plot_types : list
|
|
315
|
+
# The type of plot {surface, wireframe, point_cloud}
|
|
316
|
+
# opactity : float
|
|
317
|
+
# The opacity of the plot. 0 is fully transparent and 1 is fully opaque.
|
|
318
|
+
# color : str
|
|
319
|
+
# The 6 digit color code to plot the B-spline as.
|
|
320
|
+
# surface_texture : str = "" {"metallic", "glossy", ...}, optional
|
|
321
|
+
# The surface texture to determine how light bounces off the surface.
|
|
322
|
+
# See https://github.com/marcomusy/vedo/blob/master/examples/basic/lightings.py for options.
|
|
323
|
+
# additional_plotting_elemets : list
|
|
324
|
+
# Vedo plotting elements that may have been returned from previous plotting functions that should be plotted with this plot.
|
|
325
|
+
# show : bool
|
|
326
|
+
# A boolean on whether to show the plot or not. If the plot is not shown, the Vedo plotting element is returned.
|
|
327
|
+
# '''
|
|
328
|
+
# if self.space.num_parametric_dimensions == 1:
|
|
329
|
+
# return self.plot_curve(point_types=point_types, plot_types=plot_types, opacity=opacity, color=color,
|
|
330
|
+
# additional_plotting_elements=additional_plotting_elements, show=show)
|
|
331
|
+
# elif self.space.num_parametric_dimensions == 2:
|
|
332
|
+
# return self.plot_surface(point_types=point_types, plot_types=plot_types, opacity=opacity, color=color,
|
|
333
|
+
# surface_texture=surface_texture, additional_plotting_elements=additional_plotting_elements, show=show)
|
|
334
|
+
# elif self.space.num_parametric_dimensions == 3:
|
|
335
|
+
# return self.plot_volume(point_types=point_types, plot_types=plot_types, opacity=opacity, color=color,
|
|
336
|
+
# surface_texture=surface_texture, additional_plotting_elements=additional_plotting_elements, show=show)
|
|
337
|
+
raise NotImplementedError("I still need to implement this :(")
|
|
338
|
+
raise NotImplementedError(f"Plot method must be implemented in {type(self)} class?")
|
|
339
|
+
|
|
340
|
+
def _evaluate(self, coefficients, parametric_coordinates, parametric_derivative_orders) -> Union[csdl.Variable, npt.NDArray[np.float64]]:
|
|
341
|
+
raise NotImplementedError(f"_evaluate method must be implemented in {type(self)} class.")
|
|
342
|
+
|
|
343
|
+
class LinearFunctionSpace(FunctionSpace):
|
|
344
|
+
|
|
345
|
+
def fit(self, values:Union[csdl.Variable, np.ndarray], parametric_coordinates:np.ndarray, parametric_derivative_orders:np.ndarray=None,
|
|
346
|
+
regularization_parameter:float=None, constraint:callable=None) -> csdl.Variable:
|
|
347
|
+
'''
|
|
348
|
+
Fits the function to the given data. Either parametric coordinates or an evaluation matrix must be provided. If derivatives are used, the
|
|
349
|
+
parametric derivative orders must be provided. If both parametric coordinates and an evaluation matrix are provided, the evaluation matrix
|
|
350
|
+
will be used.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
values : csdl.Variable|np.ndarray -- shape=(num_points,num_physical_dimensions)
|
|
355
|
+
The values of the data.
|
|
356
|
+
parametric_coordinates : np.ndarray -- shape=(num_points, num_parametric_dimensions)
|
|
357
|
+
The parametric coordinates of the data.
|
|
358
|
+
parametric_derivative_orders : np.ndarray = None -- shape=(num_points, num_parametric_dimensions)
|
|
359
|
+
The derivative orders to fit.
|
|
360
|
+
basis_matrix : sps.csc_matrix|np.ndarray = None -- shape=(num_points, num_coefficients)
|
|
361
|
+
The evaluation matrix to use for fitting.
|
|
362
|
+
regularization_parameter : float = None
|
|
363
|
+
The regularization parameter to use for fitting. If None, no regularization is used.
|
|
364
|
+
|
|
365
|
+
Returns
|
|
366
|
+
-------
|
|
367
|
+
csdl.Variable
|
|
368
|
+
The coefficients of the fitted function.
|
|
369
|
+
'''
|
|
370
|
+
if constraint is not None:
|
|
371
|
+
return super().fit(values=values, parametric_coordinates=parametric_coordinates, parametric_derivative_orders=parametric_derivative_orders,
|
|
372
|
+
regularization_parameter=regularization_parameter, constraint=constraint)
|
|
373
|
+
|
|
374
|
+
if len(values.shape) > 2:
|
|
375
|
+
values = values.reshape((-1, values.shape[-1]))
|
|
376
|
+
elif len(values.shape) == 1:
|
|
377
|
+
values = values.reshape((1, -1))
|
|
378
|
+
|
|
379
|
+
if parametric_coordinates is not None:
|
|
380
|
+
try:
|
|
381
|
+
fitting_map = self.compute_fitting_map(parametric_coordinates)
|
|
382
|
+
return fitting_map @ values
|
|
383
|
+
except NotImplementedError:
|
|
384
|
+
basis_matrix = self.compute_basis_matrix(parametric_coordinates, parametric_derivative_orders)
|
|
385
|
+
fitting_matrix = basis_matrix.T.dot(basis_matrix)
|
|
386
|
+
|
|
387
|
+
if regularization_parameter is not None:
|
|
388
|
+
if sps.issparse(fitting_matrix):
|
|
389
|
+
fitting_matrix += regularization_parameter * sps.eye(fitting_matrix.shape[0]).tocsc()
|
|
390
|
+
else:
|
|
391
|
+
fitting_matrix += regularization_parameter * np.eye(fitting_matrix.shape[0])
|
|
392
|
+
|
|
393
|
+
if isinstance(values, csdl.Variable) and sps.issparse(fitting_matrix):
|
|
394
|
+
if len(values.shape) > 1:
|
|
395
|
+
coefficients = csdl.Variable(value=np.zeros((fitting_matrix.shape[0], values.shape[1])))
|
|
396
|
+
for i in range(values.shape[1]):
|
|
397
|
+
fitting_rhs = csdl.sparse.matvec(basis_matrix.T, values[:,i].reshape((values.shape[0],1)))
|
|
398
|
+
coefficients = coefficients.set(csdl.slice[:,i], csdl.solve_linear(fitting_matrix.toarray(), fitting_rhs).flatten())
|
|
399
|
+
# NOTE: # CASTING FITTING MATRIX TO DENSE BECAUSE CSDL DOESN'T HAVE SPARSE SOLVE YET
|
|
400
|
+
else:
|
|
401
|
+
fitting_rhs = csdl.sparse.matvec(basis_matrix.T, values)
|
|
402
|
+
coefficients = csdl.solve_linear(fitting_matrix.toarray(), fitting_rhs)
|
|
403
|
+
else:
|
|
404
|
+
if isinstance(values, csdl.Variable):
|
|
405
|
+
if len(values.shape) > 1:
|
|
406
|
+
coefficients = csdl.Variable(value=np.zeros((fitting_matrix.shape[0], values.shape[1])))
|
|
407
|
+
for i in csdl.frange(values.shape[1]):
|
|
408
|
+
fitting_rhs = basis_matrix.T @ values[:,i]
|
|
409
|
+
coefficients = coefficients.set(csdl.slice[:,i], csdl.solve_linear(fitting_matrix, fitting_rhs).flatten())
|
|
410
|
+
else:
|
|
411
|
+
fitting_rhs = basis_matrix.T @ values
|
|
412
|
+
coefficients = csdl.solve_linear(fitting_matrix, fitting_rhs)
|
|
413
|
+
else:
|
|
414
|
+
fitting_rhs = basis_matrix.T.dot(values)
|
|
415
|
+
if sps.issparse(fitting_matrix):
|
|
416
|
+
coefficients = np.zeros((fitting_matrix.shape[0], fitting_rhs.shape[1]))
|
|
417
|
+
if len(fitting_rhs.shape) > 1:
|
|
418
|
+
for i in range(fitting_rhs.shape[1]):
|
|
419
|
+
coefficients[:,i] = spsl.spsolve(fitting_matrix, fitting_rhs[:,i])
|
|
420
|
+
else:
|
|
421
|
+
coefficients = spsl.spsolve(fitting_matrix, fitting_rhs)
|
|
422
|
+
else:
|
|
423
|
+
coefficients = np.linalg.solve(fitting_matrix, fitting_rhs)
|
|
424
|
+
|
|
425
|
+
coefficients = coefficients.reshape(self.coefficients_shape + (values.shape[-1],))
|
|
426
|
+
|
|
427
|
+
return coefficients
|
|
428
|
+
# raise NotImplementedError(f"Fit method must be implemented in {type(self)} class.")
|
|
429
|
+
|
|
430
|
+
def _evaluate(self, coefficients, parametric_coordinates, parametric_derivative_orders):
|
|
431
|
+
'''
|
|
432
|
+
Evaluates the function.
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
parametric_coordinates : np.ndarray -- shape=(num_points, num_parametric_dimensions)
|
|
437
|
+
The coordinates at which to evaluate the function.
|
|
438
|
+
parametric_derivative_order : tuple = None -- shape=(num_points,num_parametric_dimensions)
|
|
439
|
+
The order of the parametric derivatives to evaluate.
|
|
440
|
+
coefficients : csdl.Variable = None -- shape=coefficients_shape
|
|
441
|
+
The coefficients of the function.
|
|
442
|
+
|
|
443
|
+
Returns
|
|
444
|
+
-------
|
|
445
|
+
function_values : csdl.Variable
|
|
446
|
+
The function evaluated at the given coordinates.
|
|
447
|
+
'''
|
|
448
|
+
|
|
449
|
+
basis_matrix = self.compute_basis_matrix(parametric_coordinates, parametric_derivative_orders)
|
|
450
|
+
# # values = basis_matrix @ coefficients
|
|
451
|
+
if isinstance(coefficients, csdl.Variable) and sps.issparse(basis_matrix):
|
|
452
|
+
if coefficients.shape != (basis_matrix.shape[1], coefficients.size//basis_matrix.shape[1]):
|
|
453
|
+
coefficients = coefficients.reshape((basis_matrix.shape[1], coefficients.size//basis_matrix.shape[1]))
|
|
454
|
+
# NOTE: TEMPORARY IMPLEMENTATION SINCE CSDL ONLY SUPPORTS SPARSE MATVECS AND NOT MATMATS
|
|
455
|
+
values = csdl.Variable(value=np.zeros((basis_matrix.shape[0], coefficients.shape[1])))
|
|
456
|
+
for i in csdl.frange(coefficients.shape[1]):
|
|
457
|
+
coefficients_column = coefficients[:,i].reshape((coefficients.shape[0],1))
|
|
458
|
+
values = values.set(csdl.slice[:,i], csdl.sparse.matvec(basis_matrix, coefficients_column).reshape((basis_matrix.shape[0],)))
|
|
459
|
+
else:
|
|
460
|
+
values = basis_matrix @ coefficients.reshape((basis_matrix.shape[1], -1))
|
|
461
|
+
|
|
462
|
+
if len(parametric_coordinates.shape) == 1:
|
|
463
|
+
pass # Come back to this case
|
|
464
|
+
|
|
465
|
+
if values.shape[:-1] != parametric_coordinates.shape[:-1]:
|
|
466
|
+
values = values.reshape(parametric_coordinates.shape[:-1] + (-1,))
|
|
467
|
+
|
|
468
|
+
if values.shape[0] == 1:
|
|
469
|
+
values = values[0] # Get rid of the extra dimension if only one point is evaluated
|
|
470
|
+
if values.shape[-1] == 1 and len(values.shape) > 1:
|
|
471
|
+
values = values.reshape(values.shape[:-1]) # Get rid of the extra dimension if only one physical dimension is evaluated
|
|
472
|
+
elif values.shape[-1] == 1:
|
|
473
|
+
values = values[0]
|
|
474
|
+
|
|
475
|
+
return values
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def print_why_injured_knee_is_warmer():
|
|
479
|
+
print("Because it's inflamed.")
|
|
480
|
+
print("I'm sorry. That was a bad joke.")
|
|
481
|
+
print("I'll see myself out.")
|
|
482
|
+
print("Goodbye")
|
|
File without changes
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import csdl_alpha
|
|
2
|
+
from ..spaces.operation_space import OperationFunctionSpace
|
|
3
|
+
from ..function import Function
|
|
4
|
+
from ..function_set import FunctionSet
|
|
5
|
+
import functools
|
|
6
|
+
import numpy as np
|
|
7
|
+
from typing import Union
|
|
8
|
+
|
|
9
|
+
def decorate_csdl_op(op, set_kwargs={}) -> callable:
|
|
10
|
+
def wrapper(*args, **kwargs) -> Union[Function, FunctionSet]:
|
|
11
|
+
kwargs = {**set_kwargs, **kwargs}
|
|
12
|
+
is_set = False
|
|
13
|
+
keys = []
|
|
14
|
+
functions = []
|
|
15
|
+
for arg in args:
|
|
16
|
+
if isinstance(arg, Function):
|
|
17
|
+
functions.append(arg)
|
|
18
|
+
elif isinstance(arg, FunctionSet):
|
|
19
|
+
functions.append(arg)
|
|
20
|
+
is_set = True
|
|
21
|
+
keys = arg.functions.keys()
|
|
22
|
+
|
|
23
|
+
if len(functions) == 0:
|
|
24
|
+
raise ValueError('No functions found in operation')
|
|
25
|
+
|
|
26
|
+
num_parametric_dimensions = functions[0].space.num_parametric_dimensions
|
|
27
|
+
for function in functions:
|
|
28
|
+
if function.space.num_parametric_dimensions != num_parametric_dimensions:
|
|
29
|
+
raise ValueError('All functions must have the same number of parametric dimensions')
|
|
30
|
+
|
|
31
|
+
if is_set:
|
|
32
|
+
if not isinstance(function, FunctionSet):
|
|
33
|
+
raise ValueError('Can\'t mix Function and FunctionSet in operation')
|
|
34
|
+
if function.functions.keys() != keys:
|
|
35
|
+
raise ValueError('All functions must have the same keys')
|
|
36
|
+
|
|
37
|
+
def operation(*args):
|
|
38
|
+
return op(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
if is_set:
|
|
41
|
+
return FunctionSet({key: Function(space=OperationFunctionSpace([arg.functions[key] if arg in functions else arg for arg in args], operation, num_parametric_dimensions[key]), coefficients=np.zeros(3)) for key in keys})
|
|
42
|
+
else:
|
|
43
|
+
return Function(space=OperationFunctionSpace(args, operation, num_parametric_dimensions), coefficients=np.zeros(1))
|
|
44
|
+
functools.update_wrapper(wrapper, op)
|
|
45
|
+
return wrapper
|
|
46
|
+
|
|
47
|
+
# Basic operations
|
|
48
|
+
add = decorate_csdl_op(csdl_alpha.add)
|
|
49
|
+
sub = decorate_csdl_op(csdl_alpha.sub)
|
|
50
|
+
mult = decorate_csdl_op(csdl_alpha.mult)
|
|
51
|
+
div = decorate_csdl_op(csdl_alpha.div)
|
|
52
|
+
power = decorate_csdl_op(csdl_alpha.power)
|
|
53
|
+
negate = decorate_csdl_op(csdl_alpha.negate)
|
|
54
|
+
sqrt = decorate_csdl_op(csdl_alpha.sqrt)
|
|
55
|
+
exp = decorate_csdl_op(csdl_alpha.exp)
|
|
56
|
+
log = decorate_csdl_op(csdl_alpha.log)
|
|
57
|
+
|
|
58
|
+
# min/max
|
|
59
|
+
absolute = decorate_csdl_op(csdl_alpha.absolute)
|
|
60
|
+
maximum = decorate_csdl_op(csdl_alpha.maximum, set_kwargs={'axes': (1,)})
|
|
61
|
+
minimum = decorate_csdl_op(csdl_alpha.minimum, set_kwargs={'axes': (1,)})
|
|
62
|
+
average = decorate_csdl_op(csdl_alpha.average, set_kwargs={'axes': (1,)})
|
|
63
|
+
sum = decorate_csdl_op(csdl_alpha.sum, set_kwargs={'axes': (1,)})
|
|
64
|
+
argsum = decorate_csdl_op(csdl_alpha.sum)
|
|
65
|
+
product = decorate_csdl_op(csdl_alpha.product, set_kwargs={'axes': (1,)})
|
|
66
|
+
|
|
67
|
+
# Vector operations
|
|
68
|
+
tensordot = decorate_csdl_op(csdl_alpha.tensordot, set_kwargs={'axes': (1,)})
|
|
69
|
+
cross = decorate_csdl_op(csdl_alpha.cross, set_kwargs={'axis': 1})
|
|
70
|
+
norm = decorate_csdl_op(csdl_alpha.norm, set_kwargs={'axes': (1,)})
|
|
71
|
+
|
|
72
|
+
# Trigonometric functions
|
|
73
|
+
sin = decorate_csdl_op(csdl_alpha.sin)
|
|
74
|
+
cos = decorate_csdl_op(csdl_alpha.cos)
|
|
75
|
+
tan = decorate_csdl_op(csdl_alpha.tan)
|
|
76
|
+
arcsin = decorate_csdl_op(csdl_alpha.arcsin)
|
|
77
|
+
arccos = decorate_csdl_op(csdl_alpha.arccos)
|
|
78
|
+
arctan = decorate_csdl_op(csdl_alpha.arctan)
|
|
79
|
+
sinh = decorate_csdl_op(csdl_alpha.sinh)
|
|
80
|
+
cosh = decorate_csdl_op(csdl_alpha.cosh)
|
|
81
|
+
tanh = decorate_csdl_op(csdl_alpha.tanh)
|
|
82
|
+
|
|
83
|
+
# Other
|
|
84
|
+
bessel = decorate_csdl_op(csdl_alpha.bessel)
|
|
85
|
+
# concatenate = decorate_csdl_op(csdl_alpha.concatenate, set_kwargs={'axis': 1}) # the fact that the input is a list of functions is a problem
|