feectools 0.1.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 (98) hide show
  1. feectools/__init__.py +0 -0
  2. feectools/accelerate/__init__.py +0 -0
  3. feectools/accelerate/accelerate.py +220 -0
  4. feectools/accelerate/compile_psydac.mk +52 -0
  5. feectools/api/__init__.py +0 -0
  6. feectools/api/essential_bc.py +122 -0
  7. feectools/api/fem_bilinear_form.py +2226 -0
  8. feectools/api/fem_common.py +286 -0
  9. feectools/api/fem_sum_form.py +123 -0
  10. feectools/api/settings.py +82 -0
  11. feectools/core/__init__.py +11 -0
  12. feectools/core/bsplines.py +1107 -0
  13. feectools/core/bsplines_kernels.py +1349 -0
  14. feectools/core/field_evaluation_kernels.py +5015 -0
  15. feectools/core/tests/__init__.py +0 -0
  16. feectools/core/tests/test_bsplines.py +263 -0
  17. feectools/core/tests/test_bsplines_kernel.py +40 -0
  18. feectools/core/tests/test_bsplines_pyccel.py +752 -0
  19. feectools/ddm/__init__.py +3 -0
  20. feectools/ddm/basic.py +78 -0
  21. feectools/ddm/blocking_data_exchanger.py +348 -0
  22. feectools/ddm/cart.py +1835 -0
  23. feectools/ddm/interface_data_exchanger.py +122 -0
  24. feectools/ddm/mpi.py +109 -0
  25. feectools/ddm/nonblocking_data_exchanger.py +331 -0
  26. feectools/ddm/partition.py +207 -0
  27. feectools/ddm/petsc.py +112 -0
  28. feectools/ddm/tests/__init__.py +0 -0
  29. feectools/ddm/tests/test_cart_1d.py +138 -0
  30. feectools/ddm/tests/test_cart_2d.py +164 -0
  31. feectools/ddm/tests/test_cart_3d.py +158 -0
  32. feectools/ddm/tests/test_multicart_2d.py +173 -0
  33. feectools/ddm/tests/test_partition.py +124 -0
  34. feectools/ddm/utilities.py +24 -0
  35. feectools/feec/__init__.py +0 -0
  36. feectools/feec/derivatives.py +780 -0
  37. feectools/feec/dof_kernels.py +210 -0
  38. feectools/feec/global_geometric_projectors.py +1073 -0
  39. feectools/feec/hodge.py +148 -0
  40. feectools/fem/__init__.py +0 -0
  41. feectools/fem/basic.py +465 -0
  42. feectools/fem/grid.py +181 -0
  43. feectools/fem/partitioning.py +344 -0
  44. feectools/fem/projectors.py +160 -0
  45. feectools/fem/splines.py +559 -0
  46. feectools/fem/tensor.py +1393 -0
  47. feectools/fem/tests/__init__.py +0 -0
  48. feectools/fem/tests/analytical_profiles_1d.py +100 -0
  49. feectools/fem/tests/analytical_profiles_base.py +34 -0
  50. feectools/fem/tests/splines_error_bounds.py +155 -0
  51. feectools/fem/tests/test_spline_histopolation.py +120 -0
  52. feectools/fem/tests/test_spline_interpolation.py +182 -0
  53. feectools/fem/tests/test_splines.py +184 -0
  54. feectools/fem/tests/test_splines_par.py +46 -0
  55. feectools/fem/tests/test_vector_spaces.py +150 -0
  56. feectools/fem/tests/utilities.py +47 -0
  57. feectools/fem/vector.py +729 -0
  58. feectools/linalg/__init__.py +0 -0
  59. feectools/linalg/basic.py +1386 -0
  60. feectools/linalg/block.py +1451 -0
  61. feectools/linalg/direct_solvers.py +201 -0
  62. feectools/linalg/fft.py +258 -0
  63. feectools/linalg/kernels/__init__.py +0 -0
  64. feectools/linalg/kernels/axpy_kernels.py +57 -0
  65. feectools/linalg/kernels/inner_kernels.py +100 -0
  66. feectools/linalg/kernels/matvec_kernels.py +206 -0
  67. feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
  68. feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
  69. feectools/linalg/kernels/transpose_kernels.py +263 -0
  70. feectools/linalg/kron.py +911 -0
  71. feectools/linalg/solvers.py +1914 -0
  72. feectools/linalg/sparse.py +114 -0
  73. feectools/linalg/stencil.py +2923 -0
  74. feectools/linalg/stencil_dot_kernels.py +317 -0
  75. feectools/linalg/stencil_transpose_kernels.py +372 -0
  76. feectools/linalg/tests/__init__.py +0 -0
  77. feectools/linalg/tests/test_block.py +1588 -0
  78. feectools/linalg/tests/test_fft.py +106 -0
  79. feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
  80. feectools/linalg/tests/test_linalg.py +1065 -0
  81. feectools/linalg/tests/test_matrix_free.py +128 -0
  82. feectools/linalg/tests/test_solvers.py +213 -0
  83. feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
  84. feectools/linalg/tests/test_stencil_vector.py +1036 -0
  85. feectools/linalg/tests/test_stencil_vector_space.py +440 -0
  86. feectools/linalg/topetsc.py +522 -0
  87. feectools/linalg/utilities.py +200 -0
  88. feectools/utilities/__init__.py +0 -0
  89. feectools/utilities/quadratures.py +113 -0
  90. feectools/utilities/utils.py +166 -0
  91. feectools/version.py +1 -0
  92. feectools-0.1.0.dist-info/METADATA +66 -0
  93. feectools-0.1.0.dist-info/RECORD +98 -0
  94. feectools-0.1.0.dist-info/WHEEL +5 -0
  95. feectools-0.1.0.dist-info/entry_points.txt +3 -0
  96. feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
  97. feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
  98. feectools-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1107 @@
1
+ # coding: utf-8
2
+ #
3
+ # Copyright 2018 Yaman Güçlü
4
+
5
+ """
6
+ Basic module that provides the means for evaluating the B-Splines basis
7
+ functions and their derivatives. In order to simplify automatic Fortran code
8
+ generation with Pyccel, no object-oriented features are employed.
9
+
10
+ References:
11
+
12
+ - [1] L. Piegl and W. Tiller. The NURBS Book, 2nd ed., Springer-Verlag Berlin Heidelberg GmbH, 1997.
13
+
14
+ - [2] SELALIB, Semi-Lagrangian Library. http://selalib.gforge.inria.fr
15
+
16
+ """
17
+ import numpy as np
18
+
19
+ from feectools.core.bsplines_kernels import (find_span_p,
20
+ find_spans_p,
21
+ basis_funs_p,
22
+ basis_funs_array_p,
23
+ basis_funs_1st_der_p,
24
+ basis_funs_all_ders_p,
25
+ collocation_matrix_p,
26
+ histopolation_matrix_p,
27
+ greville_p,
28
+ breakpoints_p,
29
+ elements_spans_p,
30
+ make_knots_p,
31
+ elevate_knots_p,
32
+ quadrature_grid_p,
33
+ basis_ders_on_quad_grid_p,
34
+ basis_integrals_p,
35
+ cell_index_p,
36
+ basis_ders_on_irregular_grid_p)
37
+
38
+ __all__ = ('find_span',
39
+ 'find_spans',
40
+ 'basis_funs',
41
+ 'basis_funs_array',
42
+ 'basis_funs_1st_der',
43
+ 'basis_funs_all_ders',
44
+ 'collocation_matrix',
45
+ 'histopolation_matrix',
46
+ 'breakpoints',
47
+ 'greville',
48
+ 'elements_spans',
49
+ 'make_knots',
50
+ 'elevate_knots',
51
+ 'quadrature_grid',
52
+ 'basis_integrals',
53
+ 'basis_ders_on_quad_grid',
54
+ 'cell_index',
55
+ 'basis_ders_on_irregular_grid')
56
+
57
+
58
+ #==============================================================================
59
+ def find_span(knots, degree, x):
60
+ """
61
+ Determine the knot span index at location x, given the B-Splines' knot
62
+ sequence and polynomial degree. See Algorithm A2.1 in [1].
63
+
64
+ For a degree p, the knot span index i identifies the indices [i-p:i] of all
65
+ p+1 non-zero basis functions at a given location x.
66
+
67
+ Parameters
68
+ ----------
69
+ knots : array_like
70
+ Knots sequence.
71
+
72
+ degree : int
73
+ Polynomial degree of B-splines.
74
+
75
+ x : float
76
+ Location of interest.
77
+
78
+ Returns
79
+ -------
80
+ span : int
81
+ Knot span index.
82
+ """
83
+ x = float(x)
84
+ knots = np.ascontiguousarray(knots, dtype=float)
85
+ return find_span_p(knots, degree, x)
86
+
87
+ #==============================================================================
88
+ def find_spans(knots, degree, x, out=None):
89
+ """
90
+ Determine the knot span index at a set of locations x, given the B-Splines' knot
91
+ sequence and polynomial degree. See Algorithm A2.1 in [1].
92
+
93
+ For a degree p, the knot span index i identifies the indices [i-p:i] of all
94
+ p+1 non-zero basis functions at a given location x.
95
+
96
+ Parameters
97
+ ----------
98
+ knots : array_like
99
+ Knots sequence.
100
+
101
+ degree : int
102
+ Polynomial degree of B-splines.
103
+
104
+ x : array_like of floats
105
+ Locations of interest.
106
+
107
+ out : array, optional
108
+ If provided, the result will be inserted into this array.
109
+ It should be of the appropriate shape and dtype.
110
+
111
+ Returns
112
+ -------
113
+ spans : array of ints
114
+ Knots span indexes.
115
+ """
116
+ knots = np.ascontiguousarray(knots, dtype=float)
117
+ x = np.ascontiguousarray(x, dtype=float)
118
+ if out is None:
119
+ out = np.zeros_like(x, dtype=int)
120
+ else:
121
+ assert out.shape == x.shape and out.dtype == np.dtype('int')
122
+
123
+ find_spans_p(knots, degree, x, out)
124
+ return out
125
+
126
+ #==============================================================================
127
+ def basis_funs(knots, degree, x, span, out=None):
128
+ """
129
+ Compute the non-vanishing B-splines at a unique location.
130
+
131
+ Parameters
132
+ ----------
133
+ knots : array_like of floats
134
+ Knots sequence.
135
+
136
+ degree : int
137
+ Polynomial degree of B-splines.
138
+
139
+ x : float
140
+ Evaluation point.
141
+
142
+ span : int
143
+ Knot span index.
144
+
145
+ out : array, optional
146
+ If provided, the result will be inserted into this array.
147
+ It should be of the appropriate shape and dtype.
148
+
149
+ Returns
150
+ -------
151
+ array
152
+ 1D array containing the values of ``degree + 1`` non-zero
153
+ Bsplines at location ``x``.
154
+ """
155
+ knots = np.ascontiguousarray(knots, dtype=float)
156
+ # Get native float
157
+ x = float(x)
158
+ if out is None:
159
+ out = np.zeros(degree + 1, dtype=float)
160
+ else:
161
+ assert out.shape == (degree + 1,) and out.dtype == np.dtype('float')
162
+ basis_funs_p(knots, degree, x, span, out)
163
+ return out
164
+
165
+ #==============================================================================
166
+ def basis_funs_array(knots, degree, span, x, out=None):
167
+ """Compute the non-vanishing B-splines at several locations.
168
+
169
+ Parameters
170
+ ----------
171
+ knots : array_like of floats
172
+ Knots sequence.
173
+
174
+ degree : int
175
+ Polynomial degree of B-splines.
176
+
177
+ x : array_like of floats
178
+ Evaluation points.
179
+
180
+ span : array_like of int
181
+ Knot span indexes.
182
+
183
+ out : array, optional
184
+ If provided, the result will be inserted into this array.
185
+ It should be of the appropriate shape and dtype.
186
+
187
+ Returns
188
+ -------
189
+ array
190
+ 2D array of shape ``(len(x), degree + 1)`` containing the values of ``degree + 1`` non-zero
191
+ Bsplines at each location in ``x``.
192
+ """
193
+ knots = np.ascontiguousarray(knots, dtype=float)
194
+ x = np.ascontiguousarray(x, dtype=float)
195
+ if out is None:
196
+ out = np.zeros(x.shape + (degree + 1,), dtype=float)
197
+ else:
198
+ assert out.shape == x.shape + (degree + 1,) and out.dtype == np.dtype('float')
199
+ basis_funs_array_p(knots, degree, x, span, out)
200
+ return out
201
+
202
+ #==============================================================================
203
+ def basis_funs_1st_der(knots, degree, x, span, out=None):
204
+ """
205
+ Compute the first derivative of the non-vanishing B-splines at a location.
206
+
207
+ Parameters
208
+ ----------
209
+ knots : array_like
210
+ Knots sequence.
211
+
212
+ degree : int
213
+ Polynomial degree of B-splines.
214
+
215
+ x : float
216
+ Evaluation point.
217
+
218
+ span : int
219
+ Knot span index.
220
+
221
+ out : array, optional
222
+ If provided, the result will be inserted into this array.
223
+ It should be of the appropriate shape and dtype.
224
+
225
+ Returns
226
+ -------
227
+ array
228
+ 1D array of size ``degree + 1`` containing the derivatives of the
229
+ ``degree + 1`` non-vanishing B-Splines at location x.
230
+
231
+ Notes
232
+ -----
233
+ See function 's_bsplines_non_uniform__eval_deriv' in Selalib's ([2]) source file
234
+ 'src/splines/sll_m_bsplines_non_uniform.F90'.
235
+
236
+ References
237
+ ----------
238
+ .. [2] SELALIB, Semi-Lagrangian Library. http://selalib.gforge.inria.fr
239
+ """
240
+ knots = np.ascontiguousarray(knots, dtype=float)
241
+ # Get native float to work on windows
242
+ x = float(x)
243
+ if out is None:
244
+ out = np.zeros(degree + 1, dtype=float)
245
+ else:
246
+ assert out.shape == (degree + 1,) and out.dtype == np.dtype('float')
247
+
248
+ basis_funs_1st_der_p(knots, degree, x, span, out)
249
+ return out
250
+
251
+ #==============================================================================
252
+ def basis_funs_all_ders(knots, degree, x, span, n, normalization='B', out=None):
253
+ """
254
+ Evaluate value and n derivatives at x of all basis functions with
255
+ support in interval :math:`[x_{span-1}, x_{span}]`.
256
+
257
+ If called with normalization='M', this uses M-splines instead of B-splines.
258
+
259
+ Parameters
260
+ ----------
261
+ knots : array_like
262
+ Knots sequence.
263
+
264
+ degree : int
265
+ Polynomial degree of B-splines.
266
+
267
+ x : float
268
+ Evaluation point.
269
+
270
+ span : int
271
+ Knot span index.
272
+
273
+ n : int
274
+ Max derivative of interest.
275
+
276
+ normalization: str
277
+ Set to 'B' to get B-Splines and 'M' to get M-Splines
278
+
279
+ out : array, optional
280
+ If provided, the result will be inserted into this array.
281
+ It should be of the appropriate shape and dtype.
282
+
283
+ Returns
284
+ -------
285
+ ders : array
286
+ 2D array of n+1 (from 0-th to n-th) derivatives at x of all (degree+1)
287
+ non-vanishing basis functions in given span.
288
+ ders[i,j] = (d/dx)^i B_k(x) with k=(span-degree+j),
289
+ for 0 <= i <= n and 0 <= j <= degree+1.
290
+ """
291
+ knots = np.ascontiguousarray(knots, dtype=float)
292
+ # Get native float to work on windows
293
+ x = float(x)
294
+ if out is None:
295
+ out = np.zeros((n + 1, degree + 1), dtype=float)
296
+ else:
297
+ assert out.shape == (n + 1, degree + 1) and out.dtype == np.dtype('float')
298
+
299
+ basis_funs_all_ders_p(knots, degree, x, span, n, normalization == 'M', out)
300
+ return out
301
+
302
+ #==============================================================================
303
+ def collocation_matrix(knots, degree, periodic, normalization, xgrid, out=None, multiplicity = 1):
304
+ """Computes the collocation matrix
305
+
306
+ If called with normalization='M', this uses M-splines instead of B-splines.
307
+
308
+ Parameters
309
+ ----------
310
+ knots : array_like
311
+ Knots sequence.
312
+
313
+ degree : int
314
+ Polynomial degree of spline space.
315
+
316
+ periodic : bool
317
+ True if domain is periodic, False otherwise.
318
+
319
+ normalization : str
320
+ Set to 'B' for B-splines, and 'M' for M-splines.
321
+
322
+ xgrid : array_like
323
+ Evaluation points.
324
+
325
+ out : array, optional
326
+ If provided, the result will be inserted into this array.
327
+ It should be of the appropriate shape and dtype.
328
+
329
+ multiplicity : int
330
+ Multiplicity of the knots in the knot sequence, we assume that the same
331
+ multiplicity applies to each interior knot.
332
+
333
+ Returns
334
+ -------
335
+ colloc_matrix : ndarray of floats
336
+ Array containing the collocation matrix.
337
+
338
+ Notes
339
+ -----
340
+ The collocation matrix :math:`C_ij = B_j(x_i)`, contains the
341
+ values of each B-spline basis function :math:`B_j` at all locations :math:`x_i`.
342
+ """
343
+ if xgrid.size == 1:
344
+ return np.ones((1, 1), dtype=float)
345
+
346
+ knots = np.ascontiguousarray(knots, dtype=float)
347
+ xgrid = np.ascontiguousarray(xgrid, dtype=float)
348
+ if out is None:
349
+ nb = len(knots) - degree - 1
350
+ if periodic:
351
+ nb -= degree + 1 - multiplicity
352
+
353
+ out = np.zeros((xgrid.shape[0], nb), dtype=float)
354
+ else:
355
+ assert out.shape == ((xgrid.shape[0], nb)) and out.dtype == np.dtype('float')
356
+
357
+ bool_normalization = normalization == "M"
358
+ multiplicity = int(multiplicity)
359
+
360
+ collocation_matrix_p(knots, degree, periodic, bool_normalization, xgrid, out, multiplicity=multiplicity)
361
+ return out
362
+
363
+ #==============================================================================
364
+ def histopolation_matrix(knots, degree, periodic, normalization, xgrid, multiplicity=1, check_boundary=True, out=None):
365
+ """Computes the histopolation matrix.
366
+
367
+ If called with normalization='M', this uses M-splines instead of B-splines.
368
+
369
+ Parameters
370
+ ----------
371
+ knots : array_like
372
+ Knots sequence.
373
+
374
+ degree : int
375
+ Polynomial degree of spline space.
376
+
377
+ periodic : bool
378
+ True if domain is periodic, False otherwise.
379
+
380
+ normalization : str
381
+ Set to 'B' for B-splines, and 'M' for M-splines.
382
+
383
+ xgrid : array_like
384
+ Grid points.
385
+
386
+ multiplicity : int
387
+ Multiplicity of the knots in the knot sequence, we assume that the same
388
+ multiplicity applies to each interior knot.
389
+
390
+ check_boundary : bool, default=True
391
+ If true and ``periodic``, will check the boundaries of ``xgrid``.
392
+
393
+ out : array, optional
394
+ If provided, the result will be inserted into this array.
395
+ It should be of the appropriate shape and dtype.
396
+
397
+ Returns
398
+ -------
399
+ array
400
+ Histopolation matrix
401
+
402
+ Notes
403
+ -----
404
+ The histopolation matrix :math:`H_{ij} = \\int_{x_i}^{x_{i+1}}B_j(x)\\,dx`
405
+ contains the integrals of each B-spline basis function :math:`B_j` between
406
+ two successive grid points.
407
+ """
408
+ # Check that knots are ordered (but allow repeated knots)
409
+ if not np.all(np.diff(knots) >= 0):
410
+ raise ValueError("Cannot accept knot sequence: {}".format(knots))
411
+
412
+ # Check that spline degree is non-negative integer
413
+ if not isinstance(degree, (int, np.integer)):
414
+ raise TypeError("Degree {} must be integer, got type {} instead".format(degree, type(degree)))
415
+ if degree < 0:
416
+ raise ValueError("Cannot accept negative degree: {}".format(degree))
417
+
418
+ # Check 'periodic' flag
419
+ if not isinstance(periodic, bool):
420
+ raise TypeError("Cannot accept non-boolean 'periodic' parameter: {}".format(periodic))
421
+
422
+ # Check 'normalization' option
423
+ if normalization not in ['B', 'M']:
424
+ raise ValueError("Cannot accept 'normalization' parameter: {}".format(normalization))
425
+
426
+ # Check that grid points are ordered, and do not allow repetitions
427
+ if not np.all(np.diff(xgrid) > 0):
428
+ raise ValueError("Grid points must be ordered, with no repetitions: {}".format(xgrid))
429
+
430
+ knots = np.ascontiguousarray(knots, dtype=float)
431
+ xgrid = np.ascontiguousarray(xgrid, dtype=float)
432
+ elevated_knots = elevate_knots(knots, degree, periodic, multiplicity=multiplicity)
433
+
434
+ normalization = normalization == "M"
435
+
436
+ if out is None:
437
+ if periodic:
438
+ out = np.zeros((len(xgrid), len(knots) - 2 * degree - 2 + multiplicity), dtype=float)
439
+ else:
440
+ out = np.zeros((len(xgrid) - 1, len(elevated_knots) - (degree + 1) - 1 - 1), dtype=float)
441
+ else:
442
+ if periodic:
443
+ assert out.shape == (len(xgrid), len(knots) - 2 * degree - 2 + multiplicity)
444
+ else:
445
+ assert out.shape == (len(xgrid) - 1, len(elevated_knots) - (degree + 1) - 1 - 1)
446
+ assert out.dtype == np.dtype('float')
447
+ multiplicity = int(multiplicity)
448
+ histopolation_matrix_p(knots, degree, periodic, normalization, xgrid, check_boundary, elevated_knots, out, multiplicity = multiplicity)
449
+ return out
450
+
451
+ #==============================================================================
452
+ def breakpoints(knots, degree, tol=1e-15, out=None):
453
+ """
454
+ Determine breakpoints' coordinates.
455
+
456
+ Parameters
457
+ ----------
458
+ knots : array_like
459
+ Knots sequence.
460
+
461
+ degree : int
462
+ Polynomial degree of B-splines.
463
+
464
+ tol: float
465
+ If the distance between two knots is less than tol, we assume
466
+ that they are repeated knots which correspond to the same break point.
467
+
468
+ out : array, optional
469
+ If provided, the result will be inserted into this array.
470
+ It should be of the appropriate shape and dtype.
471
+
472
+ Returns
473
+ -------
474
+ breaks : numpy.ndarray (1D)
475
+ Abscissas of all breakpoints.
476
+ """
477
+ knots = np.ascontiguousarray(knots, dtype=float)
478
+ if out is None:
479
+ out = np.zeros(len(knots), dtype=float)
480
+ else:
481
+ assert out.shape == knots.shape and out.dtype == np.dtype('float')
482
+ i_final = breakpoints_p(knots, degree, out, tol)
483
+ return out[:i_final]
484
+
485
+ #==============================================================================
486
+ def greville(knots, degree, periodic, out=None, multiplicity=1):
487
+ """
488
+ Compute coordinates of all Greville points.
489
+
490
+ Parameters
491
+ ----------
492
+ knots : array_like
493
+ Knots sequence.
494
+
495
+ degree : int
496
+ Polynomial degree of B-splines.
497
+
498
+ periodic : bool
499
+ True if domain is periodic, False otherwise.
500
+
501
+ out : array, optional
502
+ If provided, the result will be inserted into this array.
503
+ It should be of the appropriate shape and dtype.
504
+
505
+ multiplicity : int
506
+ Multiplicity of the knots in the knot sequence, we assume that the same
507
+ multiplicity applies to each interior knot.
508
+
509
+ Returns
510
+ -------
511
+ greville : numpy.ndarray (1D)
512
+ Abscissas of all Greville points.
513
+
514
+ """
515
+ knots = np.ascontiguousarray(knots, dtype=float)
516
+ if out is None:
517
+ n = len(knots) - 2 * degree - 2 + multiplicity if periodic else len(knots) - degree - 1
518
+ out = np.zeros(n)
519
+ multiplicity = int(multiplicity)
520
+ greville_p(knots, degree, periodic, out, multiplicity)
521
+ return out
522
+
523
+ #===============================================================================
524
+ def elements_spans(knots, degree, out=None):
525
+ """
526
+ Compute the index of the last non-vanishing spline on each grid element
527
+ (cell). The length of the returned array is the number of cells.
528
+
529
+ Parameters
530
+ ----------
531
+ knots : array_like
532
+ Knots sequence.
533
+
534
+ degree : int
535
+ Polynomial degree of B-splines.
536
+
537
+ out : array, optional
538
+ If provided, the result will be inserted into this array.
539
+ It should be of the appropriate shape and dtype.
540
+
541
+ Returns
542
+ -------
543
+ spans : numpy.ndarray (1D)
544
+ Index of last non-vanishing spline on each grid element.
545
+
546
+ Examples
547
+ --------
548
+ >>> import numpy as np
549
+ >>> from feectools.core.bsplines import make_knots, elements_spans
550
+
551
+ >>> p = 3 ; n = 8
552
+ >>> grid = np.arange( n-p+1 )
553
+ >>> knots = make_knots( breaks=grid, degree=p, periodic=False )
554
+ >>> spans = elements_spans( knots=knots, degree=p )
555
+ >>> spans
556
+ array([3, 4, 5, 6, 7])
557
+
558
+ Notes
559
+ -----
560
+ 1) Numbering of basis functions starts from 0, not 1;
561
+ 2) This function could be written in two lines:
562
+
563
+ breaks = breakpoints( knots, degree )
564
+ spans = np.searchsorted( knots, breaks[:-1], side='right' ) - 1
565
+
566
+ """
567
+ knots = np.ascontiguousarray(knots, dtype=float)
568
+ if out is None:
569
+ out = np.zeros(len(knots), dtype=np.int64)
570
+ else:
571
+ assert out.shape == knots.shape and out.dtype == np.dtype('int64')
572
+ i_final = elements_spans_p(knots, degree, out)
573
+ return out[:i_final]
574
+
575
+ #===============================================================================
576
+ def make_knots(breaks, degree, periodic, multiplicity=1, out=None):
577
+ """
578
+ Create spline knots from breakpoints, with appropriate boundary conditions.
579
+
580
+ If domain is periodic, knot sequence is extended by periodicity to have a
581
+ total of (n_cells-1)*mult+2p+2 knots (all break points are repeated mult
582
+ time and we add p+1-mult knots by periodicity at each side).
583
+
584
+ Otherwise, knot sequence is clamped (i.e. endpoints have multiplicity p+1).
585
+
586
+ Parameters
587
+ ----------
588
+ breaks : array_like
589
+ Coordinates of breakpoints (= cell edges); given in increasing order and
590
+ with no duplicates.
591
+
592
+ degree : int
593
+ Spline degree (= polynomial degree within each interval).
594
+
595
+ periodic : bool
596
+ True if domain is periodic, False otherwise.
597
+
598
+ multiplicity: int
599
+ Multiplicity of the knots in the knot sequence, we assume that the same
600
+ multiplicity applies to each interior knot.
601
+
602
+ out : array, optional
603
+ If provided, the result will be inserted into this array.
604
+ It should be of the appropriate shape and dtype.
605
+
606
+ Returns
607
+ -------
608
+ T : numpy.ndarray (1D)
609
+ Coordinates of spline knots.
610
+
611
+ """
612
+ # Type checking
613
+ assert isinstance( degree , int )
614
+ assert isinstance( periodic, bool )
615
+
616
+ # Consistency checks
617
+ assert len(breaks) > 1
618
+ assert all( np.diff(breaks) > 0 )
619
+ assert degree >= 0
620
+ assert 1 <= multiplicity and multiplicity <= degree + 1
621
+ # Cast potential numpy.int64 into python native int
622
+ multiplicity = int(multiplicity)
623
+
624
+ if periodic:
625
+ assert len(breaks) > degree
626
+
627
+ breaks = np.ascontiguousarray(breaks, dtype=float)
628
+ if out is None:
629
+ out = np.zeros(multiplicity * len(breaks[1:-1]) + 2 + 2 * degree)
630
+ else:
631
+ assert out.shape == (multiplicity * len(breaks[1:-1]) + 2 + 2 * degree,) \
632
+ and out.dtype == np.dtype('float')
633
+ make_knots_p(breaks, degree, periodic, out, multiplicity)
634
+
635
+ return out
636
+
637
+ #==============================================================================
638
+ def elevate_knots(knots, degree, periodic, multiplicity=1, tol=1e-15, out=None):
639
+ """
640
+ Given the knot sequence of a spline space S of degree p, compute the knot
641
+ sequence of a spline space S_0 of degree p+1 such that u' is in S for all
642
+ u in S_0.
643
+
644
+ Specifically, on bounded domains the first and last knots are repeated in
645
+ the sequence, and in the periodic case the knot sequence is extended by
646
+ periodicity.
647
+
648
+ Parameters
649
+ ----------
650
+ knots : array_like
651
+ Knots sequence of spline space of degree p.
652
+
653
+ degree : int
654
+ Spline degree (= polynomial degree within each interval).
655
+
656
+ periodic : bool
657
+ True if domain is periodic, False otherwise.
658
+
659
+ multiplicity : int
660
+ Multiplicity of the knots in the knot sequence, we assume that the same
661
+ multiplicity applies to each interior knot.
662
+
663
+ tol: float
664
+ If the distance between two knots is less than tol, we assume
665
+ that they are repeated knots which correspond to the same break point.
666
+
667
+ out : array, optional
668
+ If provided, the result will be inserted into this array.
669
+ It should be of the appropriate shape and dtype.
670
+
671
+ Returns
672
+ -------
673
+ new_knots : ndarray
674
+ Knots sequence of spline space of degree p+1.
675
+ """
676
+ multiplicity = int(multiplicity)
677
+ knots = np.ascontiguousarray(knots, dtype=float)
678
+ if out is None:
679
+ if periodic:
680
+ out = np.zeros(knots.shape[0] + 2, dtype=float)
681
+ else:
682
+ shape = 2*(degree + 2)
683
+ if len(knots) - 2 * (degree + 1) > 0:
684
+ uniques = (np.diff(knots[degree + 1:-degree - 1]) > tol).nonzero()
685
+ shape += multiplicity * (1 + uniques[0].shape[0])
686
+ out = np.zeros(shape, dtype=float)
687
+ else:
688
+ if periodic:
689
+ assert out.shape == (knots.shape[0] + 2,) and out.dtype == np.dtype('float')
690
+ else:
691
+ shape = 2*(degree + 2)
692
+ if len(knots) - 2 * (degree + 1) > 0:
693
+ uniques = (np.diff(knots[degree + 1:-degree - 1]) > tol).nonzero()
694
+ shape += multiplicity * (1 + uniques[0].shape[0])
695
+ assert out.shape == shape and out.dtype == np.dtype('float')
696
+
697
+ elevate_knots_p(knots, degree, periodic, out, multiplicity, tol)
698
+ return out
699
+
700
+ #==============================================================================
701
+ def quadrature_grid(breaks, quad_rule_x, quad_rule_w):
702
+ """
703
+ Compute the quadrature points and weights for performing integrals over
704
+ each element (interval) of the 1D domain, given a certain Gaussian
705
+ quadrature rule.
706
+
707
+ An n-point Gaussian quadrature rule for the canonical interval :math:`[-1,+1]`
708
+ and trivial weighting function :math:`\\omega(x)=1` is defined by the n abscissas
709
+ :math:`x_i` and n weights :math:`w_i` that satisfy the following identity for
710
+ polynomial functions :math:`f(x)` of degree :math:`2n-1` or less:
711
+
712
+ .. math :: \\int_{-1}^{+1} f(x) dx = \\sum_{i=0}^{n-1} w_i f(x_i)
713
+
714
+ Parameters
715
+ ----------
716
+ breaks : array_like of floats
717
+ Coordinates of spline breakpoints.
718
+
719
+ quad_rule_x : array_like of ints
720
+ Coordinates of quadrature points on canonical interval [-1,1].
721
+
722
+ quad_rule_w : array_like of ints
723
+ Weights assigned to quadrature points on canonical interval [-1,1].
724
+
725
+ Returns
726
+ -------
727
+ quad_x : 2D numpy.ndarray
728
+ Abscissas of quadrature points on each element (interval) of the 1D
729
+ domain. See notes below.
730
+
731
+ quad_w : 2D numpy.ndarray
732
+ Weights assigned to the quadrature points on each element (interval)
733
+ of the 1D domain. See notes below.
734
+
735
+ Notes
736
+ -----
737
+ Contents of 2D output arrays 'quad_x' and 'quad_w' are accessed with two
738
+ indices (ie,iq) where:
739
+
740
+ - ie is the global element index;
741
+ - iq is the local index of a quadrature point within the element.
742
+
743
+ """
744
+ # Check that input arrays have correct size
745
+ assert len(breaks) >= 2
746
+ assert len(quad_rule_x) == len(quad_rule_w)
747
+
748
+ # Check that provided quadrature rule is defined on interval [-1,1]
749
+ assert min(quad_rule_x) >= -1
750
+ assert max(quad_rule_x) <= +1
751
+
752
+ breaks = np.ascontiguousarray(breaks, dtype=float)
753
+
754
+ quad_rule_x = np.ascontiguousarray( quad_rule_x, dtype=float )
755
+ quad_rule_w = np.ascontiguousarray( quad_rule_w, dtype=float )
756
+
757
+ out1 = np.zeros((len(breaks) - 1, len(quad_rule_x)))
758
+ out2 = np.zeros_like(out1)
759
+
760
+ quadrature_grid_p(breaks, quad_rule_x, quad_rule_w, out1, out2)
761
+
762
+ return out1, out2
763
+
764
+ #==============================================================================
765
+ def basis_ders_on_quad_grid(knots, degree, quad_grid, nders, normalization, offset=0, out=None):
766
+ """
767
+ Evaluate B-Splines and their derivatives on the quadrature grid.
768
+
769
+ If called with normalization='M', this uses M-splines instead of B-splines.
770
+
771
+ Parameters
772
+ ----------
773
+ knots : array_like
774
+ Knots sequence.
775
+
776
+ degree : int
777
+ Polynomial degree of B-splines.
778
+
779
+ quad_grid: ndarray
780
+ 2D array of shape (ne, nq). Coordinates of quadrature points of
781
+ each element in 1D domain.
782
+ nders : int
783
+ Maximum derivative of interest.
784
+
785
+ normalization : str
786
+ Set to 'B' for B-splines, and 'M' for M-splines.
787
+
788
+ offset : int, default=0
789
+ Assumes that the quadrature grid starts from cell number offset.
790
+
791
+ out : array, optional
792
+ If provided, the result will be inserted into this array.
793
+ It should be of the appropriate shape and dtype.
794
+
795
+ Returns
796
+ -------
797
+ basis: ndarray
798
+ Values of B-Splines and their derivatives at quadrature points in
799
+ each element of 1D domain. Indices are
800
+ . ie: global element (0 <= ie < ne )
801
+ . il: local basis function (0 <= il <= degree)
802
+ . id: derivative (0 <= id <= nders )
803
+ . iq: local quadrature point (0 <= iq < nq )
804
+
805
+ Examples
806
+ --------
807
+ >>> knots = np.array([0.0, 0.0, 0.25, 0.5, 0.75, 1., 1.])
808
+ >>> degree = 2
809
+ >>> bk = breakpoints(knots, degree)
810
+ >>> grid = np.array([np.linspace(bk[i], bk[i+1], 4, endpoint=False) for i in range(len(bk) - 1)])
811
+ >>> basis_ders_on_quad_grid(knots, degree, grid, 0, "B")
812
+ array([[[[0.5, 0.28125, 0.125, 0.03125]],
813
+ [[0.5, 0.6875 , 0.75 , 0.6875 ]],
814
+ [[0. , 0.03125, 0.125, 0.28125]]],
815
+ [[[0.5, 0.28125, 0.125, 0.03125]],
816
+ [[0.5, 0.6875 , 0.75 , 0.6875 ]],
817
+ [[0. , 0.03125, 0.125, 0.28125]]]])
818
+ """
819
+ offset = int(offset)
820
+ ne, nq = quad_grid.shape
821
+ knots = np.ascontiguousarray(knots, dtype=float)
822
+ quad_grid = np.ascontiguousarray(quad_grid, dtype=float)
823
+ if out is None:
824
+ out = np.zeros((ne, degree + 1, nders + 1, nq), dtype=float)
825
+ else:
826
+ assert out.shape == (ne, degree + 1, nders + 1, nq) and out.dtype == np.dtype('float')
827
+ basis_ders_on_quad_grid_p(knots, degree, quad_grid, nders, normalization == 'M', offset, out)
828
+ return out
829
+
830
+
831
+ #==============================================================================
832
+ def basis_integrals(knots, degree, out=None):
833
+ """
834
+ Return the integral of each B-spline basis function over the real line:
835
+
836
+ .. math:: K[i] = \\int_{-\\infty}^{+\\infty} B_i(x) dx = (T[i+p+1]-T[i]) / (p+1).
837
+
838
+ This array can be used to convert B-splines to M-splines, which have unit
839
+ integral over the real line but no partition-of-unity property.
840
+
841
+ Parameters
842
+ ----------
843
+ knots : array_like
844
+ Knots sequence.
845
+
846
+ degree : int
847
+ Polynomial degree of B-splines.
848
+
849
+ out : array, optional
850
+ If provided, the result will be inserted into this array.
851
+ It should be of the appropriate shape and dtype.
852
+
853
+ Returns
854
+ -------
855
+ K : numpy.ndarray
856
+ Array with the integrals of each B-spline basis function.
857
+
858
+ Notes
859
+ -----
860
+ For convenience, this function does not distinguish between periodic and
861
+ non-periodic spaces, hence the length of the output array is always equal
862
+ to (len(knots)-degree-1). In the periodic case the last (degree) values in
863
+ the array are redundant, as they are a copy of the first (degree) values.
864
+ """
865
+ knots = np.ascontiguousarray(knots, dtype=float)
866
+ if out is None:
867
+ out = np.zeros(len(knots) - degree - 1, dtype=float)
868
+ else:
869
+ assert out.shape is (len(knots) - degree - 1,) and out.dtype == np.dtype('float')
870
+ basis_integrals_p(knots, degree, out)
871
+ return out
872
+
873
+ #==============================================================================
874
+ def cell_index(breaks, i_grid, tol=1e-15, out=None):
875
+ """
876
+ Computes in which cells a given array of locations belong.
877
+
878
+ Locations close to a interior breakpoint will be assumed to be
879
+ present twice in the grid, once of for each cell. Boundary breakpoints are snapped to the interior of the domain.
880
+
881
+ Parameters
882
+ ----------
883
+ breaks : array_like
884
+ Coordinates of breakpoints (= cell edges); given in increasing order and
885
+ with no duplicates.
886
+
887
+ i_grid : ndarray
888
+ 1D array of all of the points on which to evaluate the
889
+ basis functions. The points do not need to be sorted.
890
+
891
+ tol : float, default=1e-15
892
+ If the distance between a given point in ``i_grid`` and
893
+ a breakpoint is less than ``tol`` then it is considered
894
+ to be the breakpoint.
895
+
896
+ out : array, optional
897
+ If given, the result will be inserted into this array.
898
+ It should be of the appropriate shape and dtype.
899
+
900
+ Returns
901
+ -------
902
+ cell_index: ndarray
903
+ 1D array of the same shape as ``i_grid``.
904
+ ``cell_index[i]`` is the index of the cell in which
905
+ ``i_grid[i]`` belong.
906
+ """
907
+ breaks = np.ascontiguousarray(breaks, dtype=float)
908
+ i_grid = np.ascontiguousarray(i_grid, dtype=float)
909
+ if out is None:
910
+ out = np.zeros_like(i_grid, dtype=np.int64)
911
+ else:
912
+ assert out.shape == i_grid.shape and out.dtype == np.dtype('int64')
913
+ status = cell_index_p(breaks, i_grid, tol, out)
914
+ if status == -1:
915
+ raise ValueError("Encountered a point that was outside of the domain")
916
+ elif status == -2:
917
+ raise ValueError("Loop too long in cell search: a point might be slightly outside of the domain")
918
+ return out
919
+
920
+ #==============================================================================
921
+ def basis_ders_on_irregular_grid(knots, degree, i_grid, cell_index, nders, normalization, out=None):
922
+ """
923
+ Evaluate B-Splines and their derivatives on an irregular_grid.
924
+
925
+ If called with normalization='M', this uses M-splines instead of B-splines.
926
+
927
+ Parameters
928
+ ----------
929
+ knots : array_like
930
+ Knots sequence.
931
+
932
+ degree : int
933
+ Polynomial degree of B-splines.
934
+
935
+ i_grid : ndarray
936
+ 1D array of all of the points on which to evaluate the
937
+ basis functions. The points do not need to be sorted
938
+
939
+ cell_index : ndarray
940
+ 1D array of the same shape as ``i_grid``.
941
+ ``cell_index[i]`` is the index of the cell in which
942
+ ``i_grid[i]`` belong.
943
+
944
+ nders : int
945
+ Maximum derivative of interest.
946
+
947
+ normalization : str
948
+ Set to 'B' for B-splines, and 'M' for M-splines.
949
+
950
+ out : array, optional
951
+ If given, the result will be inserted into this array.
952
+ It should be of the appropriate shape and dtype.
953
+
954
+ Returns
955
+ -------
956
+ out: ndarray
957
+ 3D output array containing the values of B-Splines and their derivatives
958
+ at each point in ``i_grid``. Indices are:
959
+ . ie: location (0 <= ie < nx )
960
+ . il: local basis function (0 <= il <= degree)
961
+ . id: derivative (0 <= id <= nders )
962
+ """
963
+ knots = np.ascontiguousarray(knots, dtype=float)
964
+ i_grid = np.ascontiguousarray(i_grid, dtype=float)
965
+ if out is None:
966
+ nx = i_grid.shape[0]
967
+ out = np.zeros((nx, degree + 1, nders + 1), dtype=float)
968
+ else:
969
+ assert out.shape == (nx, degree + 1, nders + 1) and out.dtype == np.dtype('float')
970
+ basis_ders_on_irregular_grid_p(knots, degree, i_grid, cell_index, nders, normalization == 'M', out)
971
+ return out
972
+
973
+ #==============================================================================
974
+ def _refinement_matrix_one_stage(t, p, knots):
975
+ """
976
+ Computes the refinement matrix corresponding to the insertion of a given knot.
977
+
978
+ For more details see:
979
+
980
+ [1] : Les Piegl , Wayne Tiller, The NURBS Book,
981
+ https://doi.org/10.1007/978-3-642-97385-7. (Section 5.2)
982
+
983
+ Parameters
984
+ ----------
985
+ t : float
986
+ knot to be inserted.
987
+
988
+ p: int
989
+ spline degree.
990
+
991
+ knots : array_like
992
+ Knots sequence.
993
+
994
+ Returns
995
+ -------
996
+ mat : np.array[:,:]
997
+ h-refinement matrix.
998
+
999
+ new_knots : array_like
1000
+ the Knots sequence with the inserted knot.
1001
+ """
1002
+
1003
+ # ...
1004
+ def alpha_function(i, k, t, n, p, knots):
1005
+ if i <= k-p:
1006
+ alpha = 1.
1007
+
1008
+ elif (k-p < i) and (i <= k):
1009
+ alpha = (t-knots[i]) / (knots[i+p] - knots[i])
1010
+
1011
+ else:
1012
+ alpha = 0.
1013
+
1014
+ return alpha
1015
+ # ...
1016
+
1017
+ n = len(knots) - p - 1
1018
+
1019
+ mat = np.zeros((n+1,n))
1020
+
1021
+ left = find_span( knots, p, t )
1022
+
1023
+ # ...
1024
+ j = 0
1025
+ alpha = alpha_function(j, left, t, n, p, knots)
1026
+ mat[j,j] = alpha
1027
+
1028
+ for j in range(1, n):
1029
+ alpha = alpha_function(j, left, t, n, p, knots)
1030
+ mat[j,j] = alpha
1031
+ mat[j,j-1] = 1.0 - alpha
1032
+
1033
+ j = n
1034
+ alpha = alpha_function(j, left, t, n, p, knots)
1035
+ mat[j,j-1] = 1.0 - alpha
1036
+ # ...
1037
+
1038
+ # ...
1039
+ new_knots = np.zeros(n+1+p+1)
1040
+
1041
+ new_knots[:left+1] = knots[:left+1]
1042
+ new_knots[left+1] = t
1043
+ new_knots[left+2:] = knots[left+1:]
1044
+ # ...
1045
+
1046
+ return mat, new_knots
1047
+
1048
+ #==============================================================================
1049
+ def hrefinement_matrix(ts, p, knots):
1050
+ """
1051
+ Computes the refinement matrix corresponding to the insertion of a given list of knots.
1052
+
1053
+ For more details see:
1054
+
1055
+ [1] : Les Piegl , Wayne Tiller, The NURBS Book,
1056
+ https://doi.org/10.1007/978-3-642-97385-7. (Section 5.2)
1057
+
1058
+ Parameters
1059
+ ----------
1060
+ ts: np.array
1061
+ array containing the knots to be inserted
1062
+
1063
+ p: int
1064
+ spline degree.
1065
+
1066
+ knots : array_like
1067
+ Knots sequence.
1068
+
1069
+ Returns
1070
+ -------
1071
+ mat : np.array[:,:]
1072
+ h-refinement matrix
1073
+
1074
+ Examples
1075
+ --------
1076
+ >>> import numpy as np
1077
+ >>> from feectools.core.bsplines import make_knots
1078
+ >>> from feectools.core.bsplines import hrefinement_matrix
1079
+ >>> grid = np.linspace(0.,1.,5)
1080
+ >>> degree = 2
1081
+ >>> knots = make_knots(grid, degree, periodic=False)
1082
+ >>> ts = np.array([0.1, 0.2, 0.4, 0.5, 0.7, 0.8])
1083
+ >>> hrefinement_matrix(ts, p, knots)
1084
+ array([[1. , 0. , 0. , 0. , 0. , 0. ],
1085
+ [0.6 , 0.4 , 0. , 0. , 0. , 0. ],
1086
+ [0.12, 0.72, 0.16, 0. , 0. , 0. ],
1087
+ [0. , 0.6 , 0.4 , 0. , 0. , 0. ],
1088
+ [0. , 0.2 , 0.8 , 0. , 0. , 0. ],
1089
+ [0. , 0. , 0.7 , 0.3 , 0. , 0. ],
1090
+ [0. , 0. , 0.5 , 0.5 , 0. , 0. ],
1091
+ [0. , 0. , 0.1 , 0.9 , 0. , 0. ],
1092
+ [0. , 0. , 0. , 0.6 , 0.4 , 0. ],
1093
+ [0. , 0. , 0. , 0.4 , 0.6 , 0. ],
1094
+ [0. , 0. , 0. , 0. , 0.8 , 0.2 ],
1095
+ [0. , 0. , 0. , 0. , 0. , 1. ]])
1096
+ """
1097
+
1098
+ m = len(ts)
1099
+ n = len(knots) - p - 1
1100
+ out = np.eye(n)
1101
+
1102
+ for i in range(m):
1103
+ t = ts[i]
1104
+ mat, knots = _refinement_matrix_one_stage(t, p, knots)
1105
+ out = np.matmul(mat, out)
1106
+
1107
+ return out