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.
- feectools/__init__.py +0 -0
- feectools/accelerate/__init__.py +0 -0
- feectools/accelerate/accelerate.py +220 -0
- feectools/accelerate/compile_psydac.mk +52 -0
- feectools/api/__init__.py +0 -0
- feectools/api/essential_bc.py +122 -0
- feectools/api/fem_bilinear_form.py +2226 -0
- feectools/api/fem_common.py +286 -0
- feectools/api/fem_sum_form.py +123 -0
- feectools/api/settings.py +82 -0
- feectools/core/__init__.py +11 -0
- feectools/core/bsplines.py +1107 -0
- feectools/core/bsplines_kernels.py +1349 -0
- feectools/core/field_evaluation_kernels.py +5015 -0
- feectools/core/tests/__init__.py +0 -0
- feectools/core/tests/test_bsplines.py +263 -0
- feectools/core/tests/test_bsplines_kernel.py +40 -0
- feectools/core/tests/test_bsplines_pyccel.py +752 -0
- feectools/ddm/__init__.py +3 -0
- feectools/ddm/basic.py +78 -0
- feectools/ddm/blocking_data_exchanger.py +348 -0
- feectools/ddm/cart.py +1835 -0
- feectools/ddm/interface_data_exchanger.py +122 -0
- feectools/ddm/mpi.py +109 -0
- feectools/ddm/nonblocking_data_exchanger.py +331 -0
- feectools/ddm/partition.py +207 -0
- feectools/ddm/petsc.py +112 -0
- feectools/ddm/tests/__init__.py +0 -0
- feectools/ddm/tests/test_cart_1d.py +138 -0
- feectools/ddm/tests/test_cart_2d.py +164 -0
- feectools/ddm/tests/test_cart_3d.py +158 -0
- feectools/ddm/tests/test_multicart_2d.py +173 -0
- feectools/ddm/tests/test_partition.py +124 -0
- feectools/ddm/utilities.py +24 -0
- feectools/feec/__init__.py +0 -0
- feectools/feec/derivatives.py +780 -0
- feectools/feec/dof_kernels.py +210 -0
- feectools/feec/global_geometric_projectors.py +1073 -0
- feectools/feec/hodge.py +148 -0
- feectools/fem/__init__.py +0 -0
- feectools/fem/basic.py +465 -0
- feectools/fem/grid.py +181 -0
- feectools/fem/partitioning.py +344 -0
- feectools/fem/projectors.py +160 -0
- feectools/fem/splines.py +559 -0
- feectools/fem/tensor.py +1393 -0
- feectools/fem/tests/__init__.py +0 -0
- feectools/fem/tests/analytical_profiles_1d.py +100 -0
- feectools/fem/tests/analytical_profiles_base.py +34 -0
- feectools/fem/tests/splines_error_bounds.py +155 -0
- feectools/fem/tests/test_spline_histopolation.py +120 -0
- feectools/fem/tests/test_spline_interpolation.py +182 -0
- feectools/fem/tests/test_splines.py +184 -0
- feectools/fem/tests/test_splines_par.py +46 -0
- feectools/fem/tests/test_vector_spaces.py +150 -0
- feectools/fem/tests/utilities.py +47 -0
- feectools/fem/vector.py +729 -0
- feectools/linalg/__init__.py +0 -0
- feectools/linalg/basic.py +1386 -0
- feectools/linalg/block.py +1451 -0
- feectools/linalg/direct_solvers.py +201 -0
- feectools/linalg/fft.py +258 -0
- feectools/linalg/kernels/__init__.py +0 -0
- feectools/linalg/kernels/axpy_kernels.py +57 -0
- feectools/linalg/kernels/inner_kernels.py +100 -0
- feectools/linalg/kernels/matvec_kernels.py +206 -0
- feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
- feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
- feectools/linalg/kernels/transpose_kernels.py +263 -0
- feectools/linalg/kron.py +911 -0
- feectools/linalg/solvers.py +1914 -0
- feectools/linalg/sparse.py +114 -0
- feectools/linalg/stencil.py +2923 -0
- feectools/linalg/stencil_dot_kernels.py +317 -0
- feectools/linalg/stencil_transpose_kernels.py +372 -0
- feectools/linalg/tests/__init__.py +0 -0
- feectools/linalg/tests/test_block.py +1588 -0
- feectools/linalg/tests/test_fft.py +106 -0
- feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
- feectools/linalg/tests/test_linalg.py +1065 -0
- feectools/linalg/tests/test_matrix_free.py +128 -0
- feectools/linalg/tests/test_solvers.py +213 -0
- feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
- feectools/linalg/tests/test_stencil_vector.py +1036 -0
- feectools/linalg/tests/test_stencil_vector_space.py +440 -0
- feectools/linalg/topetsc.py +522 -0
- feectools/linalg/utilities.py +200 -0
- feectools/utilities/__init__.py +0 -0
- feectools/utilities/quadratures.py +113 -0
- feectools/utilities/utils.py +166 -0
- feectools/version.py +1 -0
- feectools-0.1.0.dist-info/METADATA +66 -0
- feectools-0.1.0.dist-info/RECORD +98 -0
- feectools-0.1.0.dist-info/WHEEL +5 -0
- feectools-0.1.0.dist-info/entry_points.txt +3 -0
- feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
- feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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
|