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,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
@@ -0,0 +1,5 @@
1
+ from .basic_ops import (add, sub, mult, div, negate, power, sqrt, exp, log,
2
+ absolute, maximum, minimum, average, sum, product,
3
+ tensordot, cross, norm,
4
+ sin, cos, tan, arcsin, arccos, arctan, sinh, cosh, tanh,
5
+ bessel)