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,1349 @@
1
+ # This file holds the pyccelisable versions of the functions in bsplines.py
2
+ # This will be changed once pyccel can return arrays and can get out=None arguments
3
+ # like Numpy functions.
4
+
5
+ from pyccel.decorators import pure
6
+ from numpy import shape, abs
7
+ import numpy as np
8
+ from typing import Final
9
+
10
+ # Auxiliary functions needed for the bsplines kernels.
11
+ @pure
12
+ def matmul(a: 'float[:,:]', b: 'float[:,:]', c: 'float[:,:]'):
13
+ """
14
+ Performs the matrix-matrix product a*b and writes the result into c.
15
+
16
+ Parameters
17
+ ----------
18
+ a : array[float]
19
+ The first input array (matrix).
20
+
21
+ b : array[float]
22
+ The second input array (matrix).
23
+
24
+ c : array[float]
25
+ The output array (matrix) which is the result of the matrix-matrix product a.dot(b).
26
+ """
27
+
28
+ sh_a = shape(a)
29
+ sh_b = shape(b)
30
+
31
+ c[:, :] = 0.
32
+ for i in range(sh_a[0]):
33
+ for j in range(sh_b[1]):
34
+ for k in range(sh_a[1]):
35
+ c[i, j] += a[i, k] * b[k, j]
36
+
37
+
38
+ @pure
39
+ def sum_vec(a: 'Final[float[:]]') -> float:
40
+ """
41
+ Sum the elements of a 1D vector.
42
+
43
+ Parameters
44
+ ----------
45
+ a : array[float]
46
+ The 1d vector.
47
+ """
48
+
49
+ out = 0.
50
+
51
+ sh_a = shape(a)
52
+
53
+ for i in range(sh_a[0]):
54
+ out += a[i]
55
+
56
+ return out
57
+
58
+
59
+ @pure
60
+ def min_vec(a: 'Final[float[:]]') -> float:
61
+ """
62
+ Compute the minimum a 1D vector.
63
+
64
+ Parameters
65
+ ----------
66
+ a : array[float]
67
+ The 1D vector.
68
+ """
69
+
70
+ out = a[0]
71
+
72
+ sh_a = shape(a)
73
+
74
+ for i in range(sh_a[0]):
75
+ if a[i] < out:
76
+ out = a[i]
77
+
78
+ return out
79
+
80
+
81
+ @pure
82
+ def max_vec(a: 'Final[float[:]]') -> float:
83
+ """
84
+ Compute the maximum a 1D vector.
85
+
86
+ Parameters
87
+ ----------
88
+ a : array[float]
89
+ The 1D vector.
90
+ """
91
+
92
+ out = a[0]
93
+
94
+ sh_a = shape(a)
95
+
96
+ for i in range(sh_a[0]):
97
+ if a[i] > out:
98
+ out = a[i]
99
+
100
+ return out
101
+
102
+
103
+ @pure
104
+ def max_vec_int(a: 'Final[int[:]]') -> int:
105
+ """
106
+ Compute the maximum a 1D vector.
107
+
108
+ Parameters
109
+ ----------
110
+ a : array[float]
111
+ The 1D vector.
112
+ """
113
+
114
+ out = a[0]
115
+
116
+ sh_a = shape(a)
117
+
118
+ for i in range(sh_a[0]):
119
+ if a[i] > out:
120
+ out = a[i]
121
+
122
+ return out
123
+
124
+ # =============================================================================
125
+ def find_span_p(knots: 'float[:]', degree: int, x: float):
126
+ """
127
+ Determine the knot span index at location x, given the B-Splines' knot
128
+ sequence and polynomial degree. See Algorithm A2.1 in [1].
129
+
130
+ For a degree p, the knot span index i identifies the indices [i-p:i] of all
131
+ p+1 non-zero basis functions at a given location x.
132
+
133
+ Parameters
134
+ ----------
135
+ knots : array_like
136
+ Knots sequence.
137
+
138
+ degree : int
139
+ Polynomial degree of B-splines.
140
+
141
+ x : float
142
+ Location of interest.
143
+
144
+ Returns
145
+ -------
146
+ span : int
147
+ Knot span index.
148
+
149
+ References
150
+ ----------
151
+ .. [1] L. Piegl and W. Tiller. The NURBS Book, 2nd ed.,
152
+ Springer-Verlag Berlin Heidelberg GmbH, 1997.
153
+ """
154
+ # last knot on the left boundary
155
+ low = degree
156
+ # first knot on the right boundary
157
+ high = len(knots)-1-degree
158
+
159
+ # Check if point is exactly on left/right boundary, or outside domain
160
+ if x <= knots[low ]: return low
161
+ if x >= knots[high]: return high-1
162
+
163
+ # Perform binary search
164
+ span = (low+high)//2
165
+ while x < knots[span] or x >= knots[span+1]:
166
+ if x < knots[span]:
167
+ high = span
168
+ else:
169
+ low = span
170
+ span = (low+high)//2
171
+
172
+ return span
173
+
174
+
175
+ # =============================================================================
176
+ def find_spans_p(knots: 'float[:]', degree: int, x: 'float[:]', out: 'int[:]'):
177
+ """
178
+ Determine the knot span index at a set of locations x, given the B-Splines' knot
179
+ sequence and polynomial degree. See Algorithm A2.1 in [1].
180
+
181
+ For a degree p, the knot span index i identifies the indices [i-p:i] of all
182
+ p+1 non-zero basis functions at a given location x.
183
+
184
+ Parameters
185
+ ----------
186
+ knots : array_like
187
+ Knots sequence.
188
+
189
+ degree : int
190
+ Polynomial degree of B-splines.
191
+
192
+ x : array_like of floats
193
+ Locations of interest.
194
+
195
+ out : array
196
+ The result will be inserted into this array.
197
+ It should be of the appropriate shape and dtype.
198
+ """
199
+ n = x.shape[0]
200
+
201
+ for i in range(n):
202
+ out[i] = find_span_p(knots, degree, x[i])
203
+
204
+
205
+ # =============================================================================
206
+ def basis_funs_p(knots: 'float[:]', degree: int, x: float, span: int, out: 'float[:]'):
207
+ """
208
+ Compute the non-vanishing B-splines at a unique location.
209
+
210
+ Parameters
211
+ ----------
212
+ knots : array_like of floats
213
+ Knots sequence.
214
+
215
+ degree : int
216
+ Polynomial degree of B-splines.
217
+
218
+ x : float
219
+ Evaluation point.
220
+
221
+ span : int
222
+ Knot span index.
223
+
224
+ out : array
225
+ The result will be inserted into this array.
226
+ It should be of the appropriate shape and dtype.
227
+
228
+ Notes
229
+ -----
230
+ The original Algorithm A2.2 in The NURBS Book [1] is here slightly improved
231
+ by using 'left' and 'right' temporary arrays that are one element shorter.
232
+
233
+ References
234
+ ----------
235
+ .. [1] L. Piegl and W. Tiller. The NURBS Book, 2nd ed.,
236
+ Springer-Verlag Berlin Heidelberg GmbH, 1997.
237
+ """
238
+ out[0] = 1.0
239
+ if degree == 0:
240
+ return
241
+ left = np.zeros(degree, dtype=float)
242
+ right = np.zeros(degree, dtype=float)
243
+
244
+ for j in range(degree):
245
+ left[j] = x - knots[span - j]
246
+ right[j] = knots[span + 1 + j] - x
247
+ saved = 0.0
248
+ for r in range(j + 1):
249
+ temp = out[r] / (right[r] + left[j - r])
250
+ out[r] = saved + right[r] * temp
251
+ saved = left[j - r] * temp
252
+ out[j + 1] = saved
253
+
254
+
255
+ # =============================================================================
256
+ def basis_funs_array_p(knots: 'float[:]', degree: int, x: 'float[:]', span: 'int[:]', out: 'float[:,:]'):
257
+ """
258
+ Compute the non-vanishing B-splines at locations in x, given the knot sequence,
259
+ polynomial degree and knot span. See Algorithm A2.2 in [1].
260
+
261
+ Parameters
262
+ ----------
263
+ knots : array_like of floats
264
+ Knots sequence.
265
+
266
+ degree : int
267
+ Polynomial degree of B-splines.
268
+
269
+ x : array_like of floats
270
+ Evaluation points.
271
+
272
+ span : array_like of int
273
+ Knot span indexes.
274
+
275
+ out : array
276
+ The result will be inserted into this array.
277
+ It should be of the appropriate shape and dtype.
278
+
279
+ References
280
+ ----------
281
+ .. [1] L. Piegl and W. Tiller. The NURBS Book, 2nd ed.,
282
+ Springer-Verlag Berlin Heidelberg GmbH, 1997.
283
+ """
284
+ n = x.shape[0]
285
+ for i in range(n):
286
+ basis_funs_p(knots, degree, x[i], span[i], out[i, :])
287
+
288
+
289
+ # =============================================================================
290
+ def basis_funs_1st_der_p(knots: 'float[:]', degree: int, x: float, span: int, out: 'float[:]'):
291
+ """
292
+ Compute the first derivative of the non-vanishing B-splines at a location.
293
+
294
+ Parameters
295
+ ----------
296
+ knots : array_like
297
+ Knots sequence.
298
+
299
+ degree : int
300
+ Polynomial degree of B-splines.
301
+
302
+ x : float
303
+ Evaluation point.
304
+
305
+ span : int
306
+ Knot span index.
307
+
308
+ out : array
309
+ The result will be inserted into this array.
310
+ It should be of the appropriate shape and dtype.
311
+
312
+ Notes
313
+ -----
314
+ See function 's_bsplines_non_uniform__eval_deriv' in Selalib's ([2]) source file
315
+ 'src/splines/sll_m_bsplines_non_uniform.F90'.
316
+
317
+ References
318
+ ----------
319
+ .. [2] SELALIB, Semi-Lagrangian Library. http://selalib.gforge.inria.fr
320
+ """
321
+ # For degree 0, the first derivative is constant zero
322
+ if degree == 0:
323
+ out[:] = 0
324
+ return
325
+
326
+ # Compute nonzero basis functions and knot differences for splines
327
+ # up to degree deg-1
328
+ values = np.zeros(degree)
329
+ basis_funs_p(knots, degree-1, x, span, values)
330
+
331
+ # Compute derivatives at x using formula based on difference of splines of
332
+ # degree deg-1
333
+ # -------
334
+ # j = 0
335
+ saved = degree * values[0] / (knots[span+1]-knots[span+1-degree])
336
+ out[0] = -saved
337
+ # j = 1,...,degree-1
338
+ for j in range(1,degree):
339
+ temp = saved
340
+ saved = degree * values[j] / (knots[span+j+1]-knots[span+j+1-degree])
341
+ out[j] = temp - saved
342
+ # j = degree
343
+ out[degree] = saved
344
+
345
+
346
+ # =============================================================================
347
+ def basis_funs_all_ders_p(knots: 'float[:]', degree: int, x: float, span: int, n: int, normalization: bool,
348
+ out: 'float[:,:]'):
349
+ """
350
+ Evaluate value and n derivatives at x of all basis functions with
351
+ support in interval :math:`[x_{span-1}, x_{span}]`.
352
+
353
+ If called with normalization=True, this uses M-splines instead of B-splines.
354
+
355
+ Fills a 2D array with n+1 (from 0-th to n-th) derivatives at x
356
+ of all (degree+1) non-vanishing basis functions in given span.
357
+
358
+ .. math::
359
+ ders[i,j] = \\frac{d^i}{dx^i} B_k(x) \\, \\text{with} k=(span-degree+j),
360
+ \\forall (i,j), 0 \\leq i \\leq n \\, 0 \\leq j \\leq \\text{degree}+1.
361
+
362
+ Parameters
363
+ ----------
364
+ knots : array_like
365
+ Knots sequence.
366
+
367
+ degree : int
368
+ Polynomial degree of B-splines.
369
+
370
+ x : float
371
+ Evaluation point.
372
+
373
+ span : int
374
+ Knot span index.
375
+
376
+ n : int
377
+ Max derivative of interest.
378
+
379
+ normalization: bool
380
+ Set to False to get B-Splines and True to get M-Splines
381
+
382
+ out : array
383
+ The result will be inserted into this array.
384
+ It should be of the appropriate shape and dtype.
385
+
386
+ Notes
387
+ -----
388
+ The original Algorithm A2.3 in The NURBS Book [1] is here improved:
389
+ - 'left' and 'right' arrays are 1 element shorter;
390
+ - inverse of knot differences are saved to avoid unnecessary divisions;
391
+ - innermost loops are replaced with vector operations on slices.
392
+
393
+ References
394
+ ----------
395
+ .. [1] L. Piegl and W. Tiller. The NURBS Book, 2nd ed.,
396
+ Springer-Verlag Berlin Heidelberg GmbH, 1997.
397
+ """
398
+ sh_a = np.empty(2)
399
+ sh_b = np.empty(2)
400
+ left = np.empty(degree)
401
+ right = np.empty(degree)
402
+ ndu = np.empty((degree+1, degree+1))
403
+ a = np.empty((2, degree+1))
404
+ temp_d = np.empty((1, 1))
405
+ # Number of derivatives that need to be effectively computed
406
+ # Derivatives higher than degree are = 0.
407
+ ne = min(n, degree)
408
+
409
+ # Compute nonzero basis functions and knot differences for splines
410
+ # up to degree, which are needed to compute derivatives.
411
+ # Store values in 2D temporary array 'ndu' (square matrix).
412
+
413
+ ndu[0, 0] = 1.0
414
+ for j in range(degree):
415
+ left[j] = x - knots[span-j]
416
+ right[j] = knots[span+1+j] - x
417
+ saved = 0.0
418
+ for r in range(j+1):
419
+ # compute inverse of knot differences and save them into lower triangular part of ndu
420
+ ndu[j + 1, r] = 1.0 / (right[r] + left[j - r])
421
+ # compute basis functions and save them into upper triangular part of ndu
422
+ temp = ndu[r, j] * ndu[j + 1, r]
423
+ ndu[r, j + 1] = saved + right[r] * temp
424
+ saved = left[j - r] * temp
425
+ ndu[j + 1, j + 1] = saved
426
+
427
+ # Compute derivatives in 2D output array 'out'
428
+ out[0, :] = ndu[:, degree]
429
+
430
+ for r in range(degree+1):
431
+
432
+ s1 = 0
433
+ s2 = 1
434
+ a[0, 0] = 1.0
435
+ for k in range(1, ne + 1):
436
+ d = 0.0
437
+ rk = r-k
438
+ pk = degree-k
439
+ if r >= k:
440
+ a[s2, 0] = a[s1, 0] * ndu[pk + 1, rk]
441
+ d = a[s2, 0] * ndu[rk, pk]
442
+
443
+ j1 = 1 if (rk > -1 ) else -rk
444
+ j2 = k-1 if (r-1 <= pk) else degree-r
445
+
446
+ a[s2, j1:j2 + 1] = (a[s1, j1:j2 + 1] - a[s1, j1 - 1:j2]) * ndu[pk + 1, rk + j1:rk + j2 + 1]
447
+ # temp_d[:, :] = np.matmul(a[s2:s2 + 1, j1:j2 + 1], ndu[rk + j1:rk + j2 + 1, pk: pk + 1])
448
+
449
+ sh_a[:] = shape(a[s2:s2 + 1, j1:j2 + 1])
450
+ sh_b[:] = shape(ndu[rk + j1:rk + j2 + 1, pk: pk + 1])
451
+
452
+ if sh_a[0] == 0 or sh_a[1] == 0 or sh_b[0] == 0 or sh_b[1] == 0:
453
+ temp_d[:, :] = 0.
454
+ else:
455
+ matmul(a[s2:s2 + 1, j1:j2 + 1], ndu[rk + j1:rk + j2 + 1, pk: pk + 1],temp_d[:, :])
456
+ d+= temp_d[0, 0]
457
+ if r <= pk:
458
+ a[s2, k] = - a[s1, k - 1] * ndu[pk + 1, r]
459
+ d += a[s2, k] * ndu[r, pk]
460
+
461
+ out[k, r] = d
462
+ j = s1
463
+ s1 = s2
464
+ s2 = j
465
+
466
+ # Multiply derivatives by correct factors
467
+ r = degree
468
+ for k in range(1, ne+1):
469
+ out[k, :] = out[k, :] * r
470
+ r = r * (degree-k)
471
+
472
+ if normalization:
473
+ for i in range(degree + 1):
474
+ out[:, i] *= (degree + 1) / (knots[i + span + 1] - knots[i + span - degree])
475
+
476
+
477
+ # =============================================================================
478
+ def basis_integrals_p(knots: 'float[:]', degree: int, out: 'float[:]'):
479
+ """
480
+ Return the integral of each B-spline basis function over the real line:
481
+
482
+ :math: K[i] := \\int_{-\\infty}^{+\\infty} B[i](x) dx = (T[i+p+1]-T[i]) / (p+1).
483
+
484
+ This array can be used to convert B-splines to M-splines, which have unit
485
+ integral over the real line but no partition-of-unity property.
486
+
487
+ Parameters
488
+ ----------
489
+ knots : array_like
490
+ Knots sequence.
491
+
492
+ degree : int
493
+ Polynomial degree of B-splines.
494
+
495
+ out : array
496
+ The result will be inserted into this array.
497
+ It should be of the appropriate shape and dtype.
498
+
499
+ Notes
500
+ -----
501
+ For convenience, this function does not distinguish between periodic and
502
+ non-periodic spaces, hence the length of the output array is always equal
503
+ to (len(knots)-degree-1). In the periodic case the last (degree) values in
504
+ the array are redundant, as they are a copy of the first (degree) values.
505
+
506
+ """
507
+ T = knots
508
+ p = degree
509
+ n = len(T)-p-1
510
+ for i in range(n):
511
+ out[i] = (T[i + p + 1] - T[i])/ (p + 1)
512
+
513
+
514
+ # =============================================================================
515
+ def collocation_matrix_p(knots: 'float[:]', degree: int, periodic: bool, normalization: bool, xgrid: 'float[:]',
516
+ out: 'float[:,:]', multiplicity : int = 1):
517
+ """
518
+ Compute the collocation matrix :math:`C_ij = B_j(x_i)`, which contains the
519
+ values of each B-spline basis function :math:`B_j` at all locations :math:`x_i`.
520
+
521
+ If called with normalization=True, this uses M-splines instead of B-splines.
522
+
523
+ Parameters
524
+ ----------
525
+ knots : array_like
526
+ Knots sequence.
527
+
528
+ degree : int
529
+ Polynomial degree of spline space.
530
+
531
+ periodic : bool
532
+ True if domain is periodic, False otherwise.
533
+
534
+ normalization : bool
535
+ Set to False for B-splines, and True for M-splines.
536
+
537
+ xgrid : array_like
538
+ Evaluation points.
539
+
540
+ out : array
541
+ The result will be inserted into this array.
542
+ It should be of the appropriate shape and dtype.
543
+
544
+ multiplicity : int
545
+ Multiplicity of the knots in the knot sequence, we assume that the same
546
+ multiplicity applies to each interior knot.
547
+
548
+ """
549
+ # Number of basis functions (in periodic case remove degree repeated elements)
550
+ nb = len(knots)-degree-1
551
+ if periodic:
552
+ nb -= degree + 1 - multiplicity
553
+
554
+ # Number of evaluation points
555
+ nx = len(xgrid)
556
+
557
+ basis = np.zeros((nx, degree + 1))
558
+ spans = np.zeros(nx, dtype=int)
559
+ find_spans_p(knots, degree, xgrid, spans)
560
+ basis_funs_array_p(knots, degree, xgrid, spans, basis)
561
+
562
+ # Fill in non-zero matrix values
563
+
564
+ # Rescaling of B-splines, to get M-splines if needed
565
+ if not normalization:
566
+ if periodic:
567
+ for i in range(nx):
568
+ for j in range(degree + 1):
569
+ actual_j = (spans[i] - degree + j) % nb
570
+ out[i, actual_j] = basis[i, j]
571
+ else:
572
+ for i in range(nx):
573
+ out[i, spans[i] - degree:spans[i] + 1] = basis[i, :]
574
+ else:
575
+ integrals = np.zeros(knots.shape[0] - degree - 1)
576
+ basis_integrals_p(knots, degree, integrals)
577
+ scaling = 1.0 / integrals
578
+ if periodic:
579
+ for i in range(nx):
580
+ for j in range(degree + 1):
581
+ actual_j = (spans[i] - degree + j) % nb
582
+ out[i, actual_j] = basis[i, j] * scaling[spans[i] - degree + j]
583
+
584
+ else:
585
+ for i in range(nx):
586
+ local_scaling = scaling[spans[i] - degree:spans[i] + 1]
587
+ out[i, spans[i] - degree:spans[i] + 1] = basis[i, :] * local_scaling[:]
588
+
589
+ # Mitigate round-off errors
590
+ for x in range(nx):
591
+ for y in range(nb):
592
+ if abs(out[x, y]) < 1e-14:
593
+ out[x, y] = 0.0
594
+
595
+
596
+ # =============================================================================
597
+ def histopolation_matrix_p(knots: 'float[:]', degree: int, periodic: bool, normalization: bool, xgrid: 'float[:]',
598
+ check_boundary: bool, elevated_knots: 'float[:]', out: 'float[:,:]', multiplicity : int = 1):
599
+ """Computes the histopolation matrix.
600
+
601
+ If called with normalization=True, this uses M-splines instead of B-splines.
602
+
603
+ Parameters
604
+ ----------
605
+ knots : array_like
606
+ Knots sequence.
607
+
608
+ degree : int
609
+ Polynomial degree of spline space.
610
+
611
+ periodic : bool
612
+ True if domain is periodic, False otherwise.
613
+
614
+ normalization : str
615
+ Set to False for B-splines, and True for M-splines.
616
+
617
+ xgrid : array_like
618
+ Grid points.
619
+
620
+ check_boundary : bool, default=True
621
+ If true and ``periodic``, will check the boundaries of ``xgrid``.
622
+
623
+ out : array
624
+ The result will be inserted into this array.
625
+ It should be of the appropriate shape and dtype.
626
+
627
+ multiplicity : int
628
+ Multiplicity of the knots in the knot sequence, we assume that the same
629
+ multiplicity applies to each interior knot.
630
+
631
+ Notes
632
+ -----
633
+ The histopolation matrix :math:`H_{ij} = \\int_{x_i}^{x_{i+1}}B_j(x)\\,dx`
634
+ contains the integrals of each B-spline basis function :math:`B_j` between
635
+ two successive grid points.
636
+ """
637
+ nb = len(knots) - degree - 1
638
+ if periodic:
639
+ nb -= degree + 1 - multiplicity
640
+
641
+ # Number of evaluation points
642
+ nx = len(xgrid)
643
+
644
+ # In periodic case, make sure that evaluation points include domain boundaries
645
+ xgrid_new = np.zeros(len(xgrid) + 2)
646
+ actual_len = len(xgrid)
647
+ if periodic:
648
+ if check_boundary:
649
+ xmin = knots[degree]
650
+ xmax = knots[len(knots) - 1 - degree]
651
+
652
+ if xgrid[0] > xmin and xgrid[-1] < xmax:
653
+ xgrid_new[0] = xmin
654
+ xgrid_new[1:-1] = xgrid[:]
655
+ xgrid_new[-1] = xmax
656
+ actual_len += 2
657
+
658
+ elif xgrid[0] > xmin:
659
+ xgrid_new[0] = xmin
660
+ xgrid_new[1:-1] = xgrid[:]
661
+ actual_len += 1
662
+
663
+ elif xgrid[-1] < xmax:
664
+ xgrid_new[-2] = xmax
665
+ xgrid_new[:-2] = xgrid
666
+ actual_len += 1
667
+ else:
668
+ xgrid_new[:-2] = xgrid
669
+
670
+ else:
671
+ xgrid_new[:-2] = xgrid
672
+ else:
673
+ xgrid_new[:-2] = xgrid
674
+
675
+ # B-splines of degree p+1: basis[i,j] := Bj(xi)
676
+
677
+ # NOTES:
678
+ # . cannot use M-splines in analytical formula for histopolation matrix
679
+ # . always use non-periodic splines to avoid circulant matrix structure
680
+ nb_elevated = len(elevated_knots) - (degree + 1) - 1
681
+ colloc = np.zeros((actual_len, nb_elevated))
682
+ collocation_matrix_p(elevated_knots,
683
+ degree + 1,
684
+ False,
685
+ False,
686
+ xgrid_new[:actual_len],
687
+ colloc,
688
+ multiplicity = multiplicity)
689
+
690
+ m = colloc.shape[0] - 1
691
+ n = colloc.shape[1] - 1
692
+
693
+ spans = np.zeros(colloc.shape[0], dtype=int)
694
+ for i in range(colloc.shape[0]):
695
+ local_span = 0
696
+ for j in range(colloc.shape[1]):
697
+ if abs(colloc[i, j]) != 0:
698
+ local_span = j
699
+ break
700
+ spans[i] = local_span + degree + 1
701
+
702
+ # Compute histopolation matrix from collocation matrix of higher degree
703
+ if periodic:
704
+ temp_array = np.zeros((m, n))
705
+ H = temp_array[:, :]
706
+ else:
707
+ H = out[:, :]
708
+
709
+ if normalization:
710
+ for i in range(m):
711
+ # Indices of first/last non-zero elements in row of collocation matrix
712
+ jstart = spans[i] - (degree + 1)
713
+ jend = min(spans[i + 1], n)
714
+ # Compute non-zero values of histopolation matrix
715
+ for j in range(1 + jstart, jend + 1):
716
+ # s = np.sum(colloc[i, 0:j]) - np.sum(colloc[i + 1, 0:j])
717
+ s = sum_vec(colloc[i, 0:j]) - sum_vec(colloc[i + 1, 0:j])
718
+ H[i, j - 1] = s
719
+
720
+ else:
721
+ integrals = np.zeros(knots.shape[0] - degree - 1)
722
+ basis_integrals_p(knots, degree, integrals)
723
+ for i in range(m):
724
+ # Indices of first/last non-zero elements in row of collocation matrix
725
+ jstart = spans[i] - (degree + 1)
726
+ jend = min(spans[i + 1], n)
727
+ # Compute non-zero values of histopolation matrix
728
+ for j in range(1 + jstart, jend + 1):
729
+ # s = np.sum(colloc[i, 0:j]) - np.sum(colloc[i + 1, 0:j])
730
+ s = sum_vec(colloc[i, 0:j]) - sum_vec(colloc[i + 1, 0:j])
731
+ H[i, j - 1] = s * integrals[j - 1]
732
+
733
+ # Mitigate round-off errors
734
+ for i in range(m):
735
+ for j in range(n):
736
+ if abs(H[i, j]) < 1e-14:
737
+ H[i, j] = 0.0
738
+
739
+ # Non periodic case: Stop here
740
+ if periodic:
741
+ # Periodic case: wrap around histopolation matrix
742
+ # 1. identify repeated basis functions (sum columns)
743
+ # 2. identify split interval (sum rows)
744
+ for i in range(m):
745
+ for j in range(n):
746
+ out[i % nx, j % nb] += H[i, j]
747
+
748
+
749
+ # =============================================================================
750
+ def merge_sort(a: 'float[:]'):
751
+ """Performs a 'in place' merge sort of the input list
752
+
753
+ Parameters
754
+ ----------
755
+ a : array_like
756
+ 1D array/list to sort using merge sort
757
+ """
758
+ if len(a) != 1 and len(a) != 0:
759
+ n = len(a)
760
+
761
+ a1 = np.zeros(n // 2)
762
+ a1[:] = a[:n // 2]
763
+ a2 = np.zeros(n - n // 2)
764
+ a2[:] = a[n // 2:]
765
+
766
+ merge_sort(a1)
767
+ merge_sort(a2)
768
+
769
+ i_a1 = 0
770
+ i_a2 = 0
771
+ for i in range(n):
772
+ a1_i = a1[i_a1]
773
+ a2_i = a2[i_a2]
774
+ if a1_i < a2_i:
775
+ a[i] = a1_i
776
+ i_a1 += 1
777
+ else:
778
+ a[i] = a2_i
779
+ i_a2 += 1
780
+ if i_a1 == len(a1) or i_a2 == len(a2):
781
+ last_i = i
782
+ break
783
+
784
+ for i_1 in range(n // 2 - i_a1):
785
+ a[last_i + 1 + i_1] = a1[i_a1 + i_1]
786
+
787
+ for i_2 in range(n - n // 2 - i_a2):
788
+ a[last_i + 1 + i_2] = a2[i_a2 + i_2]
789
+
790
+
791
+ # =============================================================================
792
+ def breakpoints_p(knots: 'float[:]', degree: int, out: 'float[:]', tol: float = 1e-15):
793
+ """
794
+ Determine breakpoints' coordinates.
795
+
796
+ Parameters
797
+ ----------
798
+ knots : array_like
799
+ Knots sequence.
800
+
801
+ degree : int
802
+ Polynomial degree of B-splines.
803
+
804
+ tol: float
805
+ If the distance between two knots is less than tol, we assume
806
+ that they are repeated knots which correspond to the same break point.
807
+
808
+ out : array
809
+ The result will be inserted into this array.
810
+ It should be of the appropriate shape and dtype.
811
+
812
+ Returns
813
+ -------
814
+ last_index : int
815
+ Last meaningful index + 1, e.g. the actual interesting result
816
+ is ``out[:last_index]``.
817
+ """
818
+ # knots = np.array(knots)
819
+ # diff = np.append(True, abs(np.diff(knots[degree:-degree]))>tol)
820
+ # return knots[degree:-degree][diff]
821
+
822
+ out[0] = knots[degree]
823
+ i_out = 1
824
+ for i in range(degree, len(knots) - degree - 1):
825
+ if abs(knots[i] - knots[i + 1]) > tol:
826
+ out[i_out] = knots[i + 1]
827
+ i_out += 1
828
+ return i_out
829
+
830
+
831
+ # =============================================================================
832
+ def greville_p(knots: 'float[:]', degree: int, periodic: bool, out:'float[:]', multiplicity: int=1):
833
+ """
834
+ Compute coordinates of all Greville points.
835
+
836
+ Parameters
837
+ ----------
838
+ knots : array_like
839
+ Knots sequence.
840
+
841
+ degree : int
842
+ Polynomial degree of B-splines.
843
+
844
+ periodic : bool
845
+ True if domain is periodic, False otherwise.
846
+
847
+ out : array
848
+ The result will be inserted into this array.
849
+ It should be of the appropriate shape and dtype.
850
+
851
+ multiplicity : int
852
+ Multiplicity of the knots in the knot sequence, we assume that the same
853
+ multiplicity applies to each interior knot.
854
+ """
855
+ T = knots
856
+ p = degree
857
+ n = len(T)-2*p-2 + multiplicity if periodic else len(T)-p-1
858
+
859
+ # Compute greville abscissas as average of p consecutive knot values
860
+ if p == multiplicity-1:
861
+ for i in range(n):
862
+ # out[i] = sum(T[i:i + p + 2]) / (p + 2)
863
+ out[i] = sum_vec(T[i:i + p + 2]) / (p + 2)
864
+ if abs(out[i]) < 1e-14:
865
+ out[i] = 0.0
866
+ else:
867
+ for i in range(1, 1+n):
868
+ # out[i - 1] = sum(T[i:i + p]) / p
869
+ out[i - 1] = sum_vec(T[i:i + p]) / p
870
+ if abs(out[i - 1]) < 1e-14:
871
+ out[i - 1] = 0.0
872
+
873
+ # Domain boundaries
874
+ a = T[p]
875
+ b = T[len(T) - 1 - p]
876
+
877
+ # If needed apply periodic boundary conditions, then sort array
878
+ if periodic:
879
+ out[:] = (out[:] - a) % (b-a) + a
880
+ merge_sort(out)
881
+
882
+ # Make sure roundoff errors don't push Greville points outside domain
883
+ out[0] = max(out[0], a)
884
+ out[-1] = min(out[-1], b)
885
+
886
+
887
+ # =============================================================================
888
+ def elements_spans_p(knots: 'float[:]', degree: int, out: 'int[:]'):
889
+ """
890
+ Compute the index of the last non-vanishing spline on each grid element
891
+ (cell). The length of the returned array is the number of cells.
892
+
893
+ Parameters
894
+ ----------
895
+ knots : array_like
896
+ Knots sequence.
897
+
898
+ degree : int
899
+ Polynomial degree of B-splines.
900
+
901
+ out : array
902
+ The result will be inserted into this array.
903
+ It should be of the appropriate shape and dtype.
904
+
905
+ Returns
906
+ -------
907
+ last_index : int
908
+ Last meaningful index + 1, e.g. the actual interesting result
909
+ is ``out[:last_index]``.
910
+
911
+ Notes
912
+ -----
913
+ 1) Numbering of basis functions starts from 0, not 1;
914
+ 2) This function could be written in two lines:
915
+
916
+ breaks = breakpoints( knots, degree )
917
+ spans = np.searchsorted( knots, breaks[:-1], side='right' ) - 1
918
+ """
919
+ temp_array = np.zeros(len(knots))
920
+
921
+ actual_len = breakpoints_p(knots, degree, temp_array)
922
+
923
+ nk = len(knots)
924
+ ne = actual_len - 1
925
+
926
+ ie = 0
927
+ for ik in range(degree, nk-degree):
928
+ if abs(knots[ik + 1] - knots[ik]) >= 1e-15:
929
+ out[ie] = ik
930
+ ie += 1
931
+ if ie == ne:
932
+ break
933
+
934
+ return ne
935
+
936
+
937
+ # =============================================================================
938
+ def make_knots_p(breaks: 'float[:]', degree: int, periodic: bool, out: 'float[:]', multiplicity: int = 1):
939
+ """
940
+ Create spline knots from breakpoints, with appropriate boundary conditions.
941
+
942
+ If domain is periodic, knot sequence is extended by periodicity to have a
943
+ total of (n_cells-1)*mult+2p+2 knots (all break points are repeated mult
944
+ time and we add p+1-mult knots by periodicity at each side).
945
+
946
+ Otherwise, knot sequence is clamped (i.e. endpoints have multiplicity p+1).
947
+
948
+ Parameters
949
+ ----------
950
+ breaks : array_like
951
+ Coordinates of breakpoints (= cell edges); given in increasing order and
952
+ with no duplicates.
953
+
954
+ degree : int
955
+ Spline degree (= polynomial degree within each interval).
956
+
957
+ periodic : bool
958
+ True if domain is periodic, False otherwise.
959
+
960
+ multiplicity: int
961
+ Multiplicity of the knots in the knot sequence, we assume that the same
962
+ multiplicity applies to each interior knot.
963
+
964
+ out : array
965
+ The result will be inserted into this array.
966
+ It should be of the appropriate shape and dtype.
967
+ """
968
+ ncells = len(breaks) - 1
969
+
970
+ for i in range(0, ncells+1):
971
+ out[degree + 1 + (i-1) * multiplicity :degree + 1 + i * multiplicity ] = breaks[i]
972
+
973
+ len_out = len(out)
974
+
975
+ if periodic:
976
+ period = breaks[-1]-breaks[0]
977
+
978
+ out[: degree + 1 - multiplicity] = out[len_out - 2 * (degree + 1 )+ multiplicity: len_out - (degree + 1)] - period
979
+ out[len_out - (degree + 1 - multiplicity) :] = out[degree + 1:2*(degree + 1)- multiplicity] + period
980
+
981
+
982
+ #
983
+ else:
984
+ out[0:degree + 1 - multiplicity] = breaks[0]
985
+ out[len_out - degree - 1 + multiplicity:] = breaks[-1]
986
+
987
+
988
+ # =============================================================================
989
+ def elevate_knots_p(knots: 'float[:]', degree: int, periodic: bool, out: 'float[:]',
990
+ multiplicity: int = 1,
991
+ tol: float = 1e-15):
992
+ """
993
+ Given the knot sequence of a spline space S of degree p, compute the knot
994
+ sequence of a spline space S_0 of degree p+1 such that u' is in S for all
995
+ u in S_0.
996
+
997
+ Specifically, on bounded domains the first and last knots are repeated in
998
+ the sequence, and in the periodic case the knot sequence is extended by
999
+ periodicity.
1000
+
1001
+ Parameters
1002
+ ----------
1003
+ knots : array_like
1004
+ Knots sequence of spline space of degree p.
1005
+
1006
+ degree : int
1007
+ Spline degree (= polynomial degree within each interval).
1008
+
1009
+ periodic : bool
1010
+ True if domain is periodic, False otherwise.
1011
+
1012
+ multiplicity : int
1013
+ Multiplicity of the knots in the knot sequence, we assume that the same
1014
+ multiplicity applies to each interior knot.
1015
+
1016
+ tol: float
1017
+ If the distance between two knots is less than tol, we assume
1018
+ that they are repeated knots which correspond to the same break point.
1019
+
1020
+ out : array
1021
+ The result will be inserted into this array.
1022
+ It should be of the appropriate shape and dtype.
1023
+ """
1024
+ if periodic:
1025
+ T = knots
1026
+ p = degree
1027
+ period = T[len(knots) -1 - p] - T[p]
1028
+ left = T[len(knots) -2 - 2 * p + multiplicity-1] - period
1029
+ right = T[2 * p + 2 - multiplicity] + period
1030
+
1031
+ out[0] = left
1032
+ out[-1] = right
1033
+ out[1: - 1] = knots
1034
+
1035
+ else:
1036
+ out[0] = knots[0]
1037
+ out[1:degree + 2] = knots[:degree+1]
1038
+
1039
+ n_out = len(out)
1040
+ n_knots = len(knots)
1041
+ out[n_out - degree - 2] = knots[n_knots - 1]
1042
+ out[n_out - degree - 1:n_out] = knots[n_knots - degree - 1:]
1043
+
1044
+ if len(knots[degree + 1:n_knots - degree - 1]) > 0:
1045
+ out[degree + 2: degree + 2 + multiplicity] = knots[degree + 1]
1046
+
1047
+ unique_index = 0
1048
+
1049
+ for i in range(degree + 1, n_knots - degree - 2, 1):
1050
+ if knots[i + 1] - knots[i] > tol:
1051
+ out[degree + 2 + multiplicity * (unique_index + 1):
1052
+ degree + 2 + multiplicity * (unique_index + 2)] = knots[i + 1]
1053
+ unique_index += 1
1054
+
1055
+ else:
1056
+ out[degree + 2: n_out - degree - 2] = knots[degree + 1:n_knots - degree - 1]
1057
+
1058
+
1059
+ # =============================================================================
1060
+ def quadrature_grid_p(breaks: 'float[:]', quad_rule_x: 'float[:]', quad_rule_w: 'float[:]', out1: 'float[:,:]',
1061
+ out2: 'float[:,:]'):
1062
+ """
1063
+ Compute the quadrature points and weights for performing integrals over
1064
+ each element (interval) of the 1D domain, given a certain Gaussian
1065
+ quadrature rule.
1066
+
1067
+ An n-point Gaussian quadrature rule for the canonical interval :math:`[-1,+1]`
1068
+ and trivial weighting function :math:`\\omega(x)=1` is defined by the n abscissas
1069
+ :math:`x_i` and n weights :math:`w_i` that satisfy the following identity for
1070
+ polynomial functions :math:`f(x)` of degree :math:`2n-1` or less:
1071
+
1072
+ .. math :: \\int_{-1}^{+1} f(x) dx = \\sum_{i=0}^{n-1} w_i f(x_i)
1073
+
1074
+ Parameters
1075
+ ----------
1076
+ breaks : array_like of floats
1077
+ Coordinates of spline breakpoints.
1078
+
1079
+ quad_rule_x : array_like of ints
1080
+ Coordinates of quadrature points on canonical interval [-1,1].
1081
+
1082
+ quad_rule_w : array_like of ints
1083
+ Weights assigned to quadrature points on canonical interval [-1,1].
1084
+
1085
+ out1 : array
1086
+ 2D output array where the quadrature points will be inserted.
1087
+ It should be of the appropriate shape and dtype.
1088
+
1089
+ out2 : array
1090
+ 2D output array where the quadrature weights will be inserted.
1091
+ It should be of the appropriate shape and dtype.
1092
+
1093
+ Notes
1094
+ -----
1095
+ Contents of 2D output arrays 'out1' and 'out2' are accessed with two
1096
+ indices (ie,iq) where:
1097
+
1098
+ - ie is the global element index;
1099
+ - iq is the local index of a quadrature point within the element.
1100
+
1101
+ """
1102
+ ncells = len(breaks) - 1
1103
+
1104
+ # Compute location and weight of quadrature points from basic rule
1105
+ for ie in range(ncells):
1106
+ a = breaks[ie]
1107
+ b = breaks[ie + 1]
1108
+
1109
+ c0 = 0.5 * (a + b)
1110
+ c1 = 0.5 * (b - a)
1111
+ out1[ie, :] = c1 * quad_rule_x[:] + c0
1112
+ out2[ie, :] = c1 * quad_rule_w[:]
1113
+
1114
+
1115
+ # =============================================================================
1116
+ def basis_ders_on_quad_grid_p(knots: 'float[:]', degree: int, quad_grid: 'float[:,:]', nders: int, normalization: bool,
1117
+ offset: int, out: 'float[:,:,:,:]'):
1118
+ """
1119
+ Evaluate B-Splines and their derivatives on the quadrature grid.
1120
+
1121
+ If called with normalization=True, this uses M-splines instead of B-splines.
1122
+
1123
+ Parameters
1124
+ ----------
1125
+ knots : array_like
1126
+ Knots sequence.
1127
+
1128
+ degree : int
1129
+ Polynomial degree of B-splines.
1130
+
1131
+ quad_grid: ndarray
1132
+ 2D array of shape (ne, nq). Coordinates of quadrature points of
1133
+ each element in 1D domain, which can be given by quadrature_grid()
1134
+ or chosen arbitrarily.
1135
+
1136
+ nders : int
1137
+ Maximum derivative of interest.
1138
+
1139
+ normalization : bool
1140
+ Set to False for B-splines, and True for M-splines.
1141
+
1142
+ offset : int, default=0
1143
+ Assumes that the quadrature grid starts from cell number offset.
1144
+
1145
+ out : array
1146
+ The result will be inserted into this array.
1147
+ It should be of the appropriate shape and dtype.
1148
+
1149
+ Notes
1150
+ -----
1151
+ 4D output array 'out' contains values of B-Splines and their derivatives
1152
+ at quadrature points in each element of 1D domain. Indices are
1153
+ . ie: global element (0 <= ie < ne )
1154
+ . il: local basis function (0 <= il <= degree)
1155
+ . id: derivative (0 <= id <= nders )
1156
+ . iq: local quadrature point (0 <= iq < nq )
1157
+ """
1158
+ ne = quad_grid.shape[0]
1159
+ nq = quad_grid.shape[1]
1160
+ if normalization:
1161
+ integrals = np.zeros(knots.shape[0] - degree - 1)
1162
+ basis_integrals_p(knots, degree, integrals)
1163
+ scaling = 1.0 /integrals
1164
+
1165
+ temp_spans = np.zeros(len(knots), dtype=int)
1166
+ actual_index = elements_spans_p(knots, degree, temp_spans)
1167
+ spans = temp_spans[:actual_index]
1168
+
1169
+ ders = np.zeros((nders + 1, degree + 1))
1170
+
1171
+ for ie in range(ne):
1172
+ xx = quad_grid[ie, :]
1173
+ span = spans[ie + offset]
1174
+ for iq, xq in enumerate(xx):
1175
+ basis_funs_all_ders_p(knots, degree, xq, span, nders, False, ders)
1176
+ if normalization:
1177
+ ders *= scaling[span - degree:span + 1]
1178
+ for k in range(degree + 1):
1179
+ out[ie, k, :, iq] = ders[:, k]
1180
+
1181
+
1182
+ def cell_index_p(breaks: 'float[:]', i_grid: 'float[:]', tol: float, out: 'int[:]'):
1183
+ """
1184
+ Computes in which cells a given sorted array of locations belong.
1185
+
1186
+ Locations close to a interior breakpoint will be assumed to be
1187
+ present twice in the grid, once of for each cell. Boundary breakpoints are
1188
+ snapped to the interior of the domain.
1189
+
1190
+ Parameters
1191
+ ----------
1192
+ breaks : array_like
1193
+ Coordinates of breakpoints (= cell edges); given in increasing order and
1194
+ with no duplicates.
1195
+
1196
+ i_grid : ndarray
1197
+ 1D array of locations.
1198
+
1199
+ tol : float
1200
+ If the distance between a given point in ``i_grid`` and
1201
+ a breakpoint is less than ``tol`` then it is considered
1202
+ to be the breakpoint.
1203
+
1204
+ out : array
1205
+ The result will be inserted into this array.
1206
+ It should be of the appropriate shape and dtype.
1207
+
1208
+ Returns
1209
+ -------
1210
+ status : int
1211
+ 0 if everything worked as intended, 1 if not.
1212
+
1213
+ """
1214
+ nx = len(i_grid)
1215
+
1216
+ nbk = len(breaks)
1217
+
1218
+ # Check if there are points outside the domain
1219
+ if np.min(i_grid) < breaks[0] - tol/2: return -1
1220
+ if np.max(i_grid) > breaks[nbk - 1] + tol/2: return -1
1221
+
1222
+ current_index = 0
1223
+ while current_index < nx:
1224
+ x = i_grid[current_index]
1225
+
1226
+ # Binary search
1227
+ low, high = 0, nbk - 1
1228
+ i_cell = (low + high)//2
1229
+ it = 0
1230
+ max_it = 2*(high-low) # this number of iterations should not be reached
1231
+ while it < max_it and (x < breaks[i_cell] - tol or x >= breaks[i_cell + 1] + tol):
1232
+ if x < breaks[i_cell]:
1233
+ high = i_cell
1234
+ else:
1235
+ low = i_cell
1236
+ i_cell = (low + high)//2
1237
+ it += 1
1238
+ if it >= max_it:
1239
+ return -2
1240
+
1241
+ # Check were we landed with the binary search
1242
+ # Case 1: x is the left breakpoint
1243
+ if abs(x - breaks[i_cell]) < tol:
1244
+ # Check if x is the left boundary
1245
+ if i_cell == 0:
1246
+ out[current_index] = 0
1247
+ current_index += 1
1248
+ else:
1249
+ out[current_index] = i_cell - 1
1250
+ current_index +=1
1251
+ # Check if x is the last point
1252
+ if current_index == nx:
1253
+ break
1254
+ # Check if the next point is also the left breakpoint
1255
+ # if yes put it in the next cell and move up one more point
1256
+ # in i_grid
1257
+ if abs(i_grid[current_index] - breaks[i_cell]) < tol:
1258
+ out[current_index] = i_cell
1259
+ current_index += 1
1260
+
1261
+ # Case 2: x is the right breakpoint
1262
+ elif abs(x - breaks[i_cell + 1]) < tol:
1263
+ # Check if x is the right boundary
1264
+ if i_cell + 1 == nbk - 1:
1265
+ out[current_index] = (nbk - 1) - 1
1266
+ current_index +=1
1267
+ else:
1268
+ out[current_index] = i_cell
1269
+ current_index += 1
1270
+ # Check if x is the last point
1271
+ if current_index == nx:
1272
+ break
1273
+ # Check if the next point is also the right breakpoint
1274
+ # if yes put it in the next cell and move up one more point
1275
+ # in i_grid
1276
+ if abs(i_grid[current_index] - breaks[i_cell + 1]) < tol:
1277
+ out[current_index] = i_cell + 1
1278
+ current_index +=1
1279
+ # Case 3: x is in the cell
1280
+ else:
1281
+ out[current_index] = i_cell
1282
+ current_index += 1
1283
+ return 0
1284
+
1285
+
1286
+ def basis_ders_on_irregular_grid_p(knots: 'float[:]', degree: int,
1287
+ i_grid: 'float[:]', cell_index: 'int[:]',
1288
+ nders: int, normalization: bool,
1289
+ out: 'float[:,:,:]'):
1290
+ """
1291
+ Evaluate B-Splines and their derivatives on an irregular_grid.
1292
+
1293
+ If called with normalization=True, this uses M-splines instead of B-splines.
1294
+
1295
+ Parameters
1296
+ ----------
1297
+ knots : array_like
1298
+ Knots sequence.
1299
+
1300
+ degree : int
1301
+ Polynomial degree of B-splines.
1302
+
1303
+ i_grid : ndarray
1304
+ 1D array of all of the points on which to evaluate the
1305
+ basis functions. The points do not need to be sorted
1306
+
1307
+ cell_index : ndarray
1308
+ 1D array of the same shape as ``i_grid``.
1309
+ ``cell_index[i]`` is the index of the cell in which
1310
+ ``i_grid[i]`` belong.
1311
+
1312
+ nders : int
1313
+ Maximum derivative of interest.
1314
+
1315
+ normalization : bool
1316
+ Set to False for B-splines, and True for M-splines.
1317
+
1318
+ out : array
1319
+ The result will be inserted into this array.
1320
+ It should be of the appropriate shape and dtype.
1321
+
1322
+ Notes
1323
+ -----
1324
+ 3D output array 'out' contains values of B-Splines and their derivatives
1325
+ at quadrature points in each element of 1D domain. Indices are
1326
+ . ie: location (0 <= ie < nx )
1327
+ . il: local basis function (0 <= il <= degree)
1328
+ . id: derivative (0 <= id <= nders )
1329
+ """
1330
+ nx = i_grid.shape[0]
1331
+ if normalization:
1332
+ scaling = np.zeros(knots.shape[0] - degree - 1)
1333
+ basis_integrals_p(knots, degree, scaling)
1334
+ scaling = 1.0 / scaling
1335
+
1336
+ ders = np.zeros((nders + 1, degree + 1))
1337
+
1338
+ temp_spans = np.zeros(len(knots), dtype=int)
1339
+ actual_index = elements_spans_p(knots, degree, temp_spans)
1340
+ spans = temp_spans[:actual_index]
1341
+
1342
+ for ie in range(nx):
1343
+ xx = i_grid[ie]
1344
+ span = spans[cell_index[ie]]
1345
+ basis_funs_all_ders_p(knots, degree, xx, span, nders, False, ders)
1346
+ if normalization:
1347
+ ders *= scaling[span - degree:span + 1]
1348
+ for k in range(degree + 1):
1349
+ out[ie, k, :] = ders[:, k]