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,420 @@
1
+ import csdl_alpha as csdl
2
+ import numpy as np
3
+ import jax
4
+ import jax.numpy as jnp
5
+ import warnings
6
+
7
+ from lsdo_function_spaces.core.spaces.non_cython_bsplines.compute_basis_matrix_jax import compute_basis_matrix_jax
8
+ try:
9
+ _CustomExplicitOperation = csdl.experimental.CustomExplicitOperationBeta
10
+ except AttributeError:
11
+ _CustomExplicitOperation = object
12
+
13
+
14
+ class BasisMatrixCustomOpVJP(_CustomExplicitOperation):
15
+ def __init__(
16
+ self,
17
+ knots: tuple,
18
+ degree: tuple,
19
+ coefficients_shape: tuple,
20
+ der_orders: tuple = None,
21
+ ):
22
+ super().__init__()
23
+ self.knots = knots
24
+ self.degree = degree
25
+ self.coefficients_shape = coefficients_shape
26
+ self.der_orders = der_orders
27
+
28
+ def compute_basis_matrix(parametric_coordinates):
29
+ return compute_basis_matrix_jax(
30
+ us=parametric_coordinates,
31
+ degrees=self.degree,
32
+ knot_vectors=self.knots,
33
+ der_orders=self.der_orders,
34
+ ).todense()
35
+
36
+ def compute_basis_matrix_vjp(parametric_coordinates):
37
+ return jax.vjp(
38
+ compute_basis_matrix,
39
+ parametric_coordinates,
40
+ )[1]
41
+
42
+ self.vjp_fun = compute_basis_matrix_vjp
43
+
44
+ self._cache = {}
45
+
46
+ def evaluate(self, inputs, d_outputs):
47
+ para_coords = inputs["parametric_coordinates"]
48
+ d_basis_mat = d_outputs["basis_matrix"]
49
+
50
+ self.declare_input("parametric_coordinates", para_coords)
51
+ self.declare_input("d_basis_matrix", d_basis_mat)
52
+
53
+ d_para_coords = self.create_output(
54
+ "d_parametric_coordinates",
55
+ shape=para_coords.shape,
56
+ )
57
+
58
+ d_inputs = {
59
+ "parametric_coordinates": d_para_coords,
60
+ }
61
+
62
+ return d_inputs
63
+
64
+ def compute(self, inputs, outputs):
65
+ parametric_coordinates = inputs["parametric_coordinates"]
66
+ d_basis_matrix = inputs["d_basis_matrix"]
67
+
68
+
69
+ save_name = f"para_coords_shape_{parametric_coordinates.shape}"
70
+
71
+ if save_name in self._cache:
72
+ basis_matrix_fun = self._cache[save_name]
73
+ else:
74
+ basis_matrix_fun = jax.jit(
75
+ self.vjp_fun,
76
+ )
77
+ self._cache[save_name] = basis_matrix_fun
78
+ d_parametric_coordinates = basis_matrix_fun(jnp.array(parametric_coordinates))(jnp.array(d_basis_matrix))[0]
79
+ outputs["d_parametric_coordinates"] = np.array(d_parametric_coordinates)
80
+
81
+ class BasisMatrixCustomOp(_CustomExplicitOperation):
82
+ def __init__(
83
+ self,
84
+ knots: tuple,
85
+ degree: tuple,
86
+ coefficients_shape: tuple,
87
+ der_orders: tuple = None,
88
+ ):
89
+ super().__init__()
90
+ self.knots = knots
91
+ self.degree = degree
92
+ self.coefficients_shape = coefficients_shape
93
+ self.der_orders = der_orders
94
+
95
+ def compute_basis_matrix(parametric_coordinates):
96
+ result = compute_basis_matrix_jax(
97
+ us=parametric_coordinates,
98
+ degrees=self.degree,
99
+ knot_vectors=self.knots,
100
+ der_orders=self.der_orders,
101
+ ).todense()
102
+ # Only squeeze the last axis if it's size 1 (for scalar case)
103
+ # but preserve the first axis (number of points)
104
+ if result.shape[-1] == 1:
105
+ return result.squeeze(axis=-1)
106
+ return result
107
+ self.basis_matrix_computation_function = compute_basis_matrix
108
+
109
+ self._cache = {}
110
+
111
+ def evaluate(self, parametric_coordinates):
112
+ """Compute the (DENSE!) basis matrix for the given parametric coordinates.
113
+ CSDL does not support sparse matrices (as CSDL variables), so we return a dense matrix.
114
+
115
+ Parameters
116
+ ----------
117
+ parametric_coordinates : csdl.Variable
118
+ The parametric coordinates at which to evaluate the basis functions.
119
+
120
+ Returns
121
+ -------
122
+ csdl.Variable
123
+ A dense matrix containing the evaluated basis functions.
124
+ """
125
+ self.declare_input("parametric_coordinates", parametric_coordinates)
126
+
127
+ basis_matrix = self.create_output(
128
+ "basis_matrix",
129
+ shape=(parametric_coordinates.shape[0], np.prod(self.coefficients_shape)),
130
+ )
131
+ self.basis_matrix_shape = basis_matrix.shape
132
+
133
+ self.declare_vjp_function(
134
+ BasisMatrixCustomOpVJP,
135
+ knots=self.knots,
136
+ degree=self.degree,
137
+ coefficients_shape=self.coefficients_shape,
138
+ der_orders=self.der_orders,
139
+ )
140
+
141
+ return basis_matrix
142
+
143
+ def compute(self, inputs, outputs):
144
+ parametric_coordinates = inputs["parametric_coordinates"]
145
+
146
+ save_name = f"para_coords_shape_{parametric_coordinates.shape}"
147
+
148
+ if save_name in self._cache:
149
+ basis_matrix_fun = self._cache[save_name]
150
+ else:
151
+ basis_matrix_fun = jax.jit(
152
+ self.basis_matrix_computation_function,
153
+ )
154
+ self._cache[save_name] = basis_matrix_fun
155
+
156
+ basis_matrix = basis_matrix_fun(jnp.array(parametric_coordinates))
157
+ outputs["basis_matrix"] = np.array(basis_matrix).reshape(self.basis_matrix_shape)
158
+
159
+ class BSplineEvalCustomOpVJP(_CustomExplicitOperation):
160
+ def __init__(
161
+ self,
162
+ knots,
163
+ degree,
164
+ coefficients_shape,
165
+ der_orders=None
166
+ ):
167
+ super().__init__()
168
+ self.knots = knots
169
+ self.degree = degree
170
+ self.coefficients_shape = coefficients_shape
171
+ self.der_orders = der_orders
172
+
173
+ def evluate_b_spline(us, p, knots_jnp, coeffs, der_orders=None):
174
+ ndim = len(p)
175
+ if der_orders is None:
176
+ der_orders = (0,) * ndim
177
+ num_phys_dims = coeffs.shape[-1]
178
+ basis_matrix = compute_basis_matrix_jax(us, p, knots_jnp, der_orders)
179
+ return basis_matrix @ coeffs.reshape(-1, num_phys_dims)
180
+
181
+ def evaluate_b_spline_jax_wrapped(us, coeffs):
182
+ return evluate_b_spline(us, self.degree, self.knots, coeffs, self.der_orders)
183
+
184
+ def compute_vjp(us, coeffs):
185
+ return jax.vjp(
186
+ evaluate_b_spline_jax_wrapped,
187
+ us,
188
+ coeffs,
189
+ )[1]
190
+
191
+ self.vjp_fun = compute_vjp
192
+
193
+ self._cache = {}
194
+
195
+ def evaluate(self, inputs, d_outputs):
196
+ parametric_coordinates = inputs["parametric_coordinates"]
197
+ coefficients = inputs["coefficients"]
198
+ d_b_spline_values = d_outputs["b_spline_values"]
199
+
200
+ self.declare_input("parametric_coordinates", parametric_coordinates)
201
+ self.declare_input("coefficients", coefficients)
202
+ self.declare_input("d_b_spline_values", d_b_spline_values)
203
+
204
+ d_parametric_coordinates = self.create_output(
205
+ "d_parametric_coordinates",
206
+ shape=parametric_coordinates.shape,
207
+ )
208
+
209
+ d_coefficients = self.create_output(
210
+ "d_coefficients",
211
+ shape=coefficients.shape,
212
+ )
213
+
214
+ d_inputs = {
215
+ "parametric_coordinates": d_parametric_coordinates,
216
+ "coefficients": d_coefficients,
217
+ }
218
+
219
+ return d_inputs
220
+
221
+ def compute(self, inputs, outputs):
222
+ parametric_coordinates = inputs["parametric_coordinates"]
223
+ coefficients = inputs["coefficients"]
224
+ d_b_spline_values = inputs["d_b_spline_values"]
225
+
226
+ save_name = f"para_coords_shape_{parametric_coordinates.shape}_coeffs_shape_{coefficients.shape}"
227
+
228
+ if save_name in self._cache:
229
+ evaluate_b_spline_jax = self._cache[save_name]
230
+ else:
231
+ evaluate_b_spline_jax = jax.jit(
232
+ self.vjp_fun,
233
+ )
234
+ self._cache[save_name] = evaluate_b_spline_jax
235
+
236
+ d_parametric_coordinates, d_coefficients = evaluate_b_spline_jax(
237
+ jnp.array(parametric_coordinates),
238
+ jnp.array(coefficients),
239
+ )(jnp.array(d_b_spline_values))
240
+
241
+ outputs["d_parametric_coordinates"] = np.array(d_parametric_coordinates)
242
+ outputs["d_coefficients"] = np.array(d_coefficients)
243
+
244
+ class BSplineEvalCustomOp(_CustomExplicitOperation):
245
+ def __init__(self, knots, degree, coefficients_shape, der_orders=None):
246
+ super().__init__()
247
+ self.knots = tuple(jnp.array(knots_i) for knots_i in knots)
248
+ self.degree = degree
249
+ self.coefficients_shape = coefficients_shape
250
+ self.der_orders = der_orders
251
+
252
+
253
+ def evluate_b_spline(us, p, knots_jnp, coeffs, der_orders=None):
254
+ ndim = len(p)
255
+ if der_orders is None:
256
+ der_orders = (0,) * ndim
257
+ num_phys_dims = coeffs.shape[-1]
258
+ basis_matrix = compute_basis_matrix_jax(us, p, knots_jnp, der_orders)
259
+ return basis_matrix @ coeffs.reshape(-1, num_phys_dims)
260
+
261
+ def evaluate_b_spline_jax_wrapped(us, coeffs):
262
+ return evluate_b_spline(us, self.degree, self.knots, coeffs, self.der_orders).squeeze()
263
+
264
+ self.evaluate_b_spline_jax = evaluate_b_spline_jax_wrapped
265
+
266
+ self._cache = {}
267
+
268
+
269
+ def evaluate(self, parametric_coordinates, coefficients):
270
+ """Evaluate the B-spline basis functions at the given parametric coordinates.
271
+
272
+ Parameters
273
+ ----------
274
+ parametric_coordinates : csdl.Variable
275
+ The parametric coordinates at which to evaluate the basis functions.
276
+ shape=(num_points, num_parametric_dimensions)
277
+
278
+ coefficients : csdl.Variable
279
+ The coefficients of the B-spline basis functions.
280
+ shape=(ncp_x, ncp_y, ..., num_physical_dimensions)
281
+
282
+ Returns
283
+ -------
284
+ csdl.Variable
285
+ The evaluated B-spline values at the given parametric coordinates.
286
+ shape=(num_points, num_physical_dimensions)
287
+ """
288
+ self.declare_input("parametric_coordinates", parametric_coordinates)
289
+ self.declare_input("coefficients", coefficients)
290
+
291
+ num_eval_points = parametric_coordinates.shape[0]
292
+ num_physical_dimensions = coefficients.shape[-1]
293
+ output_shape = (num_eval_points, num_physical_dimensions)
294
+
295
+ b_spline_values = self.create_output("b_spline_values", shape=output_shape)
296
+
297
+ self.declare_vjp_function(
298
+ BSplineEvalCustomOpVJP,
299
+ knots=self.knots,
300
+ degree=self.degree,
301
+ coefficients_shape=self.coefficients_shape,
302
+ der_orders=self.der_orders,
303
+ )
304
+
305
+ return b_spline_values
306
+
307
+ def compute(self, inputs, outputs):
308
+ parametric_coordinates = inputs["parametric_coordinates"]
309
+ coefficients = inputs["coefficients"]
310
+
311
+ save_name = f"para_coords_shape_{parametric_coordinates.shape}_coeffs_shape_{coefficients.shape}"
312
+
313
+ if save_name in self._cache:
314
+ evaluate_b_spline_jax = self._cache[save_name]
315
+ else:
316
+ evaluate_b_spline_jax = jax.jit(
317
+ self.evaluate_b_spline_jax,
318
+ )
319
+ self._cache[save_name] = evaluate_b_spline_jax
320
+
321
+ b_spline_values = evaluate_b_spline_jax(
322
+ jnp.array(parametric_coordinates),
323
+ jnp.array(coefficients),
324
+ )
325
+ outputs["b_spline_values"] = np.array(b_spline_values).reshape(
326
+ (parametric_coordinates.shape[0], coefficients.shape[-1])
327
+ )
328
+
329
+
330
+ if __name__ == "__main__":
331
+ np.random.seed(42) # For reproducibility
332
+ import lsdo_function_spaces as lfs
333
+ import time
334
+
335
+ # Define the B-spline space parameters
336
+ num_cp_x = 10
337
+ num_cp_y = 8
338
+ nx = num_cp_x - 1 # Number of control points - 1
339
+ ny = num_cp_y - 1 # Number of control points - 1
340
+ px = 3 # Degree of the B-spline
341
+ py = 2 # Degree of the B-spline
342
+ p = (px, py)
343
+ coefficients_shape = (num_cp_x, num_cp_y)
344
+ derivative_orders = (1, 0) # # derivatives for the evaluation
345
+
346
+ knots_x = np.concatenate(
347
+ [np.zeros(px),
348
+ np.linspace(0, 1, num_cp_x - px + 1),
349
+ np.ones(px)]
350
+ )
351
+ knots_y = np.concatenate(
352
+ [np.zeros(py),
353
+ np.linspace(0, 1, num_cp_y - py + 1),
354
+ np.ones(py)]
355
+ )
356
+
357
+ knots = (knots_x, knots_y) # Knot vectors for each dimension
358
+ knots_jnp = (jnp.array(knots_x), jnp.array(knots_y)) # JAX-compatible knot vectors
359
+
360
+ coeffs_x, coeffs_y = np.meshgrid(np.linspace(0, 5, num_cp_x), np.linspace(0, 2, num_cp_y), indexing='ij')
361
+ coeffs = np.array(np.stack((coeffs_x, coeffs_y, 0.2 * np.random.rand(num_cp_x, num_cp_y)), axis=-1))
362
+
363
+ rec = csdl.Recorder(inline=True)
364
+ rec.start()
365
+
366
+ coeffs_csdl = csdl.Variable(
367
+ name="coefficients",
368
+ value=coeffs,
369
+ )
370
+ coeffs_csdl.set_as_design_variable()
371
+
372
+ num_para_coords = 20 # NOTE: the actual number is squared
373
+ epsilon = 1e-8
374
+ u, v = np.meshgrid(np.linspace(epsilon, 1-epsilon, num_para_coords),
375
+ np.linspace(epsilon, 1-epsilon, num_para_coords), indexing='ij')
376
+ us = np.array(np.stack((u.flatten(), v.flatten()), axis=-1))
377
+
378
+ us_csdl = csdl.Variable(name="parametric_coordinates", value=us)
379
+ us_csdl.set_as_design_variable()
380
+
381
+ b_spline_eval_comp = BSplineEvalCustomOp(
382
+ knots=knots_jnp,
383
+ degree=p,
384
+ coefficients_shape=coefficients_shape,
385
+ der_orders=derivative_orders,
386
+ )
387
+ new_eval_points = b_spline_eval_comp.evaluate(us_csdl, coeffs_csdl)
388
+ objective = csdl.sum((new_eval_points))
389
+ objective.set_as_objective()
390
+
391
+ jax_sim = csdl.experimental.JaxSimulator(
392
+ rec,
393
+ )
394
+
395
+ jax_sim.check_optimization_derivatives(step_size=epsilon)
396
+
397
+ b_spline_space_old = lfs.BSplineSpace(
398
+ num_parametric_dimensions=2,
399
+ degree=p,
400
+ coefficients_shape=coefficients_shape,
401
+ )
402
+ old_b_spline_fun = lfs.Function(
403
+ space=b_spline_space_old,
404
+ coefficients=coeffs_csdl,
405
+ )
406
+ old_eval_points = old_b_spline_fun.evaluate(
407
+ parametric_coordinates=us,
408
+ parametric_derivative_orders=derivative_orders,
409
+ )
410
+
411
+ # compare the results
412
+ print("Evaluated points (new):", new_eval_points.value)
413
+ print("Evaluated points (old):", old_eval_points.value)
414
+ # Check if the results are the same
415
+ if np.allclose(new_eval_points.value, old_eval_points.value):
416
+ print("The evaluated points are equal.")
417
+ else:
418
+ print("The evaluated points are NOT equal.")
419
+ print("Max difference:", np.max(np.abs(new_eval_points.value - old_eval_points.value)))
420
+