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,752 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from feectools.utilities.quadratures import gauss_legendre
|
|
5
|
+
from feectools.core.bsplines import (find_span,
|
|
6
|
+
basis_funs,
|
|
7
|
+
basis_funs_1st_der,
|
|
8
|
+
basis_funs_all_ders,
|
|
9
|
+
collocation_matrix,
|
|
10
|
+
histopolation_matrix,
|
|
11
|
+
breakpoints,
|
|
12
|
+
greville,
|
|
13
|
+
elements_spans,
|
|
14
|
+
make_knots,
|
|
15
|
+
elevate_knots,
|
|
16
|
+
quadrature_grid,
|
|
17
|
+
basis_integrals,
|
|
18
|
+
basis_ders_on_quad_grid)
|
|
19
|
+
|
|
20
|
+
# The pytest-xdist plugin requires that every worker sees the same parameters
|
|
21
|
+
# in the unit tests. As in this module random parameters are used, here we set
|
|
22
|
+
# the same random seed for all workers.
|
|
23
|
+
np.random.seed(0)
|
|
24
|
+
|
|
25
|
+
###############################################################################
|
|
26
|
+
# "True" Functions
|
|
27
|
+
###############################################################################
|
|
28
|
+
|
|
29
|
+
def find_span_true( knots, degree, x ):
|
|
30
|
+
# Knot index at left/right boundary
|
|
31
|
+
low = degree
|
|
32
|
+
high = len(knots)-1-degree
|
|
33
|
+
|
|
34
|
+
# Check if point is exactly on left/right boundary, or outside domain
|
|
35
|
+
if x <= knots[low ]: return low
|
|
36
|
+
if x >= knots[high]: return high-1
|
|
37
|
+
|
|
38
|
+
# Perform binary search
|
|
39
|
+
span = (low+high)//2
|
|
40
|
+
while x < knots[span] or x >= knots[span+1]:
|
|
41
|
+
if x < knots[span]:
|
|
42
|
+
high = span
|
|
43
|
+
else:
|
|
44
|
+
low = span
|
|
45
|
+
span = (low+high)//2
|
|
46
|
+
|
|
47
|
+
return span
|
|
48
|
+
|
|
49
|
+
#==============================================================================
|
|
50
|
+
def basis_funs_true( knots, degree, x, span ):
|
|
51
|
+
left = np.empty( degree , dtype=float )
|
|
52
|
+
right = np.empty( degree , dtype=float )
|
|
53
|
+
values = np.empty( degree+1, dtype=float )
|
|
54
|
+
|
|
55
|
+
values[0] = 1.0
|
|
56
|
+
for j in range(0,degree):
|
|
57
|
+
left [j] = x - knots[span-j]
|
|
58
|
+
right[j] = knots[span+1+j] - x
|
|
59
|
+
saved = 0.0
|
|
60
|
+
for r in range(0,j+1):
|
|
61
|
+
temp = values[r] / (right[r] + left[j-r])
|
|
62
|
+
values[r] = saved + right[r] * temp
|
|
63
|
+
saved = left[j-r] * temp
|
|
64
|
+
values[j+1] = saved
|
|
65
|
+
|
|
66
|
+
return values
|
|
67
|
+
|
|
68
|
+
#==============================================================================
|
|
69
|
+
def basis_funs_1st_der_true( knots, degree, x, span ):
|
|
70
|
+
# Compute nonzero basis functions and knot differences for splines
|
|
71
|
+
# up to degree deg-1
|
|
72
|
+
values = basis_funs_true( knots, degree-1, x, span )
|
|
73
|
+
|
|
74
|
+
# Compute derivatives at x using formula based on difference of splines of
|
|
75
|
+
# degree deg-1
|
|
76
|
+
# -------
|
|
77
|
+
# j = 0
|
|
78
|
+
ders = np.empty( degree+1, dtype=float )
|
|
79
|
+
saved = degree * values[0] / (knots[span+1]-knots[span+1-degree])
|
|
80
|
+
ders[0] = -saved
|
|
81
|
+
# j = 1,...,degree-1
|
|
82
|
+
for j in range(1,degree):
|
|
83
|
+
temp = saved
|
|
84
|
+
saved = degree * values[j] / (knots[span+j+1]-knots[span+j+1-degree])
|
|
85
|
+
ders[j] = temp - saved
|
|
86
|
+
# j = degree
|
|
87
|
+
ders[degree] = saved
|
|
88
|
+
|
|
89
|
+
return ders
|
|
90
|
+
|
|
91
|
+
#==============================================================================
|
|
92
|
+
def basis_funs_all_ders_true(knots, degree, x, span, n, normalization='B'):
|
|
93
|
+
"""
|
|
94
|
+
Evaluate value and n derivatives at x of all basis functions with
|
|
95
|
+
support in interval [x_{span-1}, x_{span}].
|
|
96
|
+
If called with normalization='M', this uses M-splines instead of B-splines.
|
|
97
|
+
ders[i,j] = (d/dx)^i B_k(x) with k=(span-degree+j),
|
|
98
|
+
for 0 <= i <= n and 0 <= j <= degree+1.
|
|
99
|
+
Parameters
|
|
100
|
+
----------
|
|
101
|
+
knots : array_like
|
|
102
|
+
Knots sequence.
|
|
103
|
+
degree : int
|
|
104
|
+
Polynomial degree of B-splines.
|
|
105
|
+
x : float
|
|
106
|
+
Evaluation point.
|
|
107
|
+
span : int
|
|
108
|
+
Knot span index.
|
|
109
|
+
n : int
|
|
110
|
+
Max derivative of interest.
|
|
111
|
+
normalization: str
|
|
112
|
+
Set to 'B' to get B-Splines and 'M' to get M-Splines
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
ders : numpy.ndarray (n+1,degree+1)
|
|
116
|
+
2D array of n+1 (from 0-th to n-th) derivatives at x of all (degree+1)
|
|
117
|
+
non-vanishing basis functions in given span.
|
|
118
|
+
Notes
|
|
119
|
+
-----
|
|
120
|
+
The original Algorithm A2.3 in The NURBS Book [1] is here improved:
|
|
121
|
+
- 'left' and 'right' arrays are 1 element shorter;
|
|
122
|
+
- inverse of knot differences are saved to avoid unnecessary divisions;
|
|
123
|
+
- innermost loops are replaced with vector operations on slices.
|
|
124
|
+
"""
|
|
125
|
+
left = np.empty( degree )
|
|
126
|
+
right = np.empty( degree )
|
|
127
|
+
ndu = np.empty( (degree+1, degree+1) )
|
|
128
|
+
a = np.empty( ( 2, degree+1) )
|
|
129
|
+
ders = np.zeros( ( n+1, degree+1) ) # output array
|
|
130
|
+
|
|
131
|
+
# Number of derivatives that need to be effectively computed
|
|
132
|
+
# Derivatives higher than degree are = 0.
|
|
133
|
+
ne = min( n, degree )
|
|
134
|
+
|
|
135
|
+
# Compute nonzero basis functions and knot differences for splines
|
|
136
|
+
# up to degree, which are needed to compute derivatives.
|
|
137
|
+
# Store values in 2D temporary array 'ndu' (square matrix).
|
|
138
|
+
ndu[0,0] = 1.0
|
|
139
|
+
for j in range(0,degree):
|
|
140
|
+
left [j] = x - knots[span-j]
|
|
141
|
+
right[j] = knots[span+1+j] - x
|
|
142
|
+
saved = 0.0
|
|
143
|
+
for r in range(0,j+1):
|
|
144
|
+
# compute inverse of knot differences and save them into lower triangular part of ndu
|
|
145
|
+
ndu[j+1,r] = 1.0 / (right[r] + left[j-r])
|
|
146
|
+
# compute basis functions and save them into upper triangular part of ndu
|
|
147
|
+
temp = ndu[r,j] * ndu[j+1,r]
|
|
148
|
+
ndu[r,j+1] = saved + right[r] * temp
|
|
149
|
+
saved = left[j-r] * temp
|
|
150
|
+
ndu[j+1,j+1] = saved
|
|
151
|
+
|
|
152
|
+
# Compute derivatives in 2D output array 'ders'
|
|
153
|
+
ders[0,:] = ndu[:,degree]
|
|
154
|
+
for r in range(0,degree+1):
|
|
155
|
+
s1 = 0
|
|
156
|
+
s2 = 1
|
|
157
|
+
a[0,0] = 1.0
|
|
158
|
+
for k in range(1,ne+1):
|
|
159
|
+
d = 0.0
|
|
160
|
+
rk = r-k
|
|
161
|
+
pk = degree-k
|
|
162
|
+
if r >= k:
|
|
163
|
+
a[s2,0] = a[s1,0] * ndu[pk+1,rk]
|
|
164
|
+
d = a[s2,0] * ndu[rk,pk]
|
|
165
|
+
j1 = 1 if (rk > -1 ) else -rk
|
|
166
|
+
j2 = k-1 if (r-1 <= pk) else degree-r
|
|
167
|
+
a[s2,j1:j2+1] = (a[s1,j1:j2+1] - a[s1,j1-1:j2]) * ndu[pk+1,rk+j1:rk+j2+1]
|
|
168
|
+
d += np.dot( a[s2,j1:j2+1], ndu[rk+j1:rk+j2+1,pk] )
|
|
169
|
+
if r <= pk:
|
|
170
|
+
a[s2,k] = - a[s1,k-1] * ndu[pk+1,r]
|
|
171
|
+
d += a[s2,k] * ndu[r,pk]
|
|
172
|
+
ders[k,r] = d
|
|
173
|
+
j = s1
|
|
174
|
+
s1 = s2
|
|
175
|
+
s2 = j
|
|
176
|
+
|
|
177
|
+
# Multiply derivatives by correct factors
|
|
178
|
+
r = degree
|
|
179
|
+
for k in range(1,ne+1):
|
|
180
|
+
ders[k,:] = ders[k,:] * r
|
|
181
|
+
r = r * (degree-k)
|
|
182
|
+
|
|
183
|
+
# Normalization to get M-Splines
|
|
184
|
+
if normalization == 'M':
|
|
185
|
+
ders *= [(degree + 1) / (knots[i + degree + 1] - knots[i]) \
|
|
186
|
+
for i in range(span - degree, span + 1)]
|
|
187
|
+
return ders
|
|
188
|
+
|
|
189
|
+
#==============================================================================
|
|
190
|
+
def collocation_matrix_true(knots, degree, periodic, normalization, xgrid):
|
|
191
|
+
# Number of basis functions (in periodic case remove degree repeated elements)
|
|
192
|
+
nb = len(knots)-degree-1
|
|
193
|
+
if periodic:
|
|
194
|
+
nb -= degree
|
|
195
|
+
|
|
196
|
+
# Number of evaluation points
|
|
197
|
+
nx = len(xgrid)
|
|
198
|
+
|
|
199
|
+
# Collocation matrix as 2D Numpy array (dense storage)
|
|
200
|
+
mat = np.zeros( (nx,nb) )
|
|
201
|
+
|
|
202
|
+
# Indexing of basis functions (periodic or not) for a given span
|
|
203
|
+
if periodic:
|
|
204
|
+
js = lambda span: [(span-degree+s) % nb for s in range( degree+1 )]
|
|
205
|
+
else:
|
|
206
|
+
js = lambda span: slice( span-degree, span+1 )
|
|
207
|
+
|
|
208
|
+
# Rescaling of B-splines, to get M-splines if needed
|
|
209
|
+
if normalization == 'B':
|
|
210
|
+
normalize = lambda basis, span: basis
|
|
211
|
+
elif normalization == 'M':
|
|
212
|
+
scaling = 1 / basis_integrals_true(knots, degree)
|
|
213
|
+
normalize = lambda basis, span: basis * scaling[span-degree: span+1]
|
|
214
|
+
|
|
215
|
+
# Fill in non-zero matrix values
|
|
216
|
+
for i,x in enumerate( xgrid ):
|
|
217
|
+
span = find_span_true( knots, degree, x )
|
|
218
|
+
basis = basis_funs_true( knots, degree, x, span )
|
|
219
|
+
mat[i,js(span)] = normalize(basis, span)
|
|
220
|
+
|
|
221
|
+
# Mitigate round-off errors
|
|
222
|
+
mat[abs(mat) < 1e-14] = 0.0
|
|
223
|
+
|
|
224
|
+
return mat
|
|
225
|
+
|
|
226
|
+
#==============================================================================
|
|
227
|
+
def histopolation_matrix_true(knots, degree, periodic, normalization, xgrid):
|
|
228
|
+
# Check that knots are ordered (but allow repeated knots)
|
|
229
|
+
if not np.all(np.diff(knots) >= 0):
|
|
230
|
+
raise ValueError("Cannot accept knot sequence: {}".format(knots))
|
|
231
|
+
|
|
232
|
+
# Check that spline degree is non-negative integer
|
|
233
|
+
if not isinstance(degree, (int, np.integer)):
|
|
234
|
+
raise TypeError("Degree {} must be integer, got type {} instead".format(degree, type(degree)))
|
|
235
|
+
if degree < 0:
|
|
236
|
+
raise ValueError("Cannot accept negative degree: {}".format(degree))
|
|
237
|
+
|
|
238
|
+
# Check 'periodic' flag
|
|
239
|
+
if not isinstance(periodic, bool):
|
|
240
|
+
raise TypeError("Cannot accept non-boolean 'periodic' parameter: {}".format(periodic))
|
|
241
|
+
|
|
242
|
+
# Check 'normalization' option
|
|
243
|
+
if normalization not in ['B', 'M']:
|
|
244
|
+
raise ValueError("Cannot accept 'normalization' parameter: {}".format(normalization))
|
|
245
|
+
|
|
246
|
+
# Check that grid points are ordered, and do not allow repetitions
|
|
247
|
+
if not np.all(np.diff(xgrid) > 0):
|
|
248
|
+
raise ValueError("Grid points must be ordered, with no repetitions: {}".format(xgrid))
|
|
249
|
+
|
|
250
|
+
# Number of basis functions (in periodic case remove degree repeated elements)
|
|
251
|
+
nb = len(knots)-degree-1
|
|
252
|
+
if periodic:
|
|
253
|
+
nb -= degree
|
|
254
|
+
|
|
255
|
+
# Number of evaluation points
|
|
256
|
+
nx = len(xgrid)
|
|
257
|
+
|
|
258
|
+
# In periodic case, make sure that evaluation points include domain boundaries
|
|
259
|
+
# TODO: only do this if the user asks for it!
|
|
260
|
+
if periodic:
|
|
261
|
+
xmin = knots[degree]
|
|
262
|
+
xmax = knots[-1-degree]
|
|
263
|
+
if xgrid[0] > xmin:
|
|
264
|
+
xgrid = [xmin, *xgrid]
|
|
265
|
+
if xgrid[-1] < xmax:
|
|
266
|
+
xgrid = [*xgrid, xmax]
|
|
267
|
+
|
|
268
|
+
# B-splines of degree p+1: basis[i,j] := Bj(xi)
|
|
269
|
+
#
|
|
270
|
+
# NOTES:
|
|
271
|
+
# . cannot use M-splines in analytical formula for histopolation matrix
|
|
272
|
+
# . always use non-periodic splines to avoid circulant matrix structure
|
|
273
|
+
C = collocation_matrix_true(
|
|
274
|
+
knots = elevate_knots_true(knots, degree, periodic),
|
|
275
|
+
degree = degree + 1,
|
|
276
|
+
periodic = False,
|
|
277
|
+
normalization = 'B',
|
|
278
|
+
xgrid = xgrid
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Rescaling of M-splines, to get B-splines if needed
|
|
282
|
+
if normalization == 'M':
|
|
283
|
+
normalize = lambda bi, j: bi
|
|
284
|
+
elif normalization == 'B':
|
|
285
|
+
scaling = basis_integrals_true(knots, degree)
|
|
286
|
+
normalize = lambda bi, j: bi * scaling[j]
|
|
287
|
+
|
|
288
|
+
# Compute span for each row (index of last non-zero basis function)
|
|
289
|
+
# TODO: would be better to have this ready beforehand
|
|
290
|
+
# TODO: use tolerance instead of comparing against zero
|
|
291
|
+
spans = [(row != 0).argmax() + (degree+1) for row in C]
|
|
292
|
+
|
|
293
|
+
# Compute histopolation matrix from collocation matrix of higher degree
|
|
294
|
+
m = C.shape[0] - 1
|
|
295
|
+
n = C.shape[1] - 1
|
|
296
|
+
H = np.zeros((m, n))
|
|
297
|
+
for i in range(m):
|
|
298
|
+
# Indices of first/last non-zero elements in row of collocation matrix
|
|
299
|
+
jstart = spans[i] - (degree+1)
|
|
300
|
+
jend = min(spans[i+1], n)
|
|
301
|
+
# Compute non-zero values of histopolation matrix
|
|
302
|
+
for j in range(1+jstart, jend+1):
|
|
303
|
+
s = C[i, 0:j].sum() - C[i+1, 0:j].sum()
|
|
304
|
+
H[i, j-1] = normalize(s, j-1)
|
|
305
|
+
|
|
306
|
+
# Mitigate round-off errors
|
|
307
|
+
H[abs(H) < 1e-14] = 0.0
|
|
308
|
+
# Non periodic case: stop here
|
|
309
|
+
if not periodic:
|
|
310
|
+
return H
|
|
311
|
+
|
|
312
|
+
# Periodic case: wrap around histopolation matrix
|
|
313
|
+
# 1. identify repeated basis functions (sum columns)
|
|
314
|
+
# 2. identify split interval (sum rows)
|
|
315
|
+
Hp = np.zeros((nx, nb))
|
|
316
|
+
for i in range(m):
|
|
317
|
+
for j in range(n):
|
|
318
|
+
Hp[i % nx, j % nb] += H[i, j]
|
|
319
|
+
|
|
320
|
+
return Hp
|
|
321
|
+
|
|
322
|
+
#==============================================================================
|
|
323
|
+
def breakpoints_true( knots, degree ,tol=1e-15):
|
|
324
|
+
knots = np.array(knots)
|
|
325
|
+
diff = np.append(True, abs(np.diff(knots[degree:-degree]))>tol)
|
|
326
|
+
return knots[degree:-degree][diff]
|
|
327
|
+
|
|
328
|
+
#==============================================================================
|
|
329
|
+
def greville_true( knots, degree, periodic ):
|
|
330
|
+
T = knots
|
|
331
|
+
p = degree
|
|
332
|
+
n = len(T)-2*p-1 if periodic else len(T)-p-1
|
|
333
|
+
|
|
334
|
+
# Compute greville abscissas as average of p consecutive knot values
|
|
335
|
+
xg = np.array([sum(T[i:i+p])/p for i in range(1,1+n)])
|
|
336
|
+
|
|
337
|
+
# Domain boundaries
|
|
338
|
+
a = T[p]
|
|
339
|
+
b = T[-1-p]
|
|
340
|
+
|
|
341
|
+
# If needed apply periodic boundary conditions, then sort array
|
|
342
|
+
if periodic:
|
|
343
|
+
xg = (xg-a) % (b-a) + a
|
|
344
|
+
xg = xg[np.argsort(xg)]
|
|
345
|
+
|
|
346
|
+
# Make sure roundoff errors don't push Greville points outside domain
|
|
347
|
+
xg[ 0] = max(xg[ 0], a)
|
|
348
|
+
xg[-1] = min(xg[-1], b)
|
|
349
|
+
|
|
350
|
+
return xg
|
|
351
|
+
|
|
352
|
+
#===============================================================================
|
|
353
|
+
def elements_spans_true( knots, degree ):
|
|
354
|
+
breaks = breakpoints_true( knots, degree )
|
|
355
|
+
nk = len(knots)
|
|
356
|
+
ne = len(breaks)-1
|
|
357
|
+
spans = np.zeros( ne, dtype=int )
|
|
358
|
+
|
|
359
|
+
ie = 0
|
|
360
|
+
for ik in range( degree, nk-degree ):
|
|
361
|
+
if knots[ik+1]-knots[ik]>=1e-15:
|
|
362
|
+
spans[ie] = ik
|
|
363
|
+
ie += 1
|
|
364
|
+
if ie == ne:
|
|
365
|
+
break
|
|
366
|
+
|
|
367
|
+
return spans
|
|
368
|
+
|
|
369
|
+
#===============================================================================
|
|
370
|
+
def make_knots_true( breaks, degree, periodic, multiplicity=1 ):
|
|
371
|
+
# Type checking
|
|
372
|
+
assert isinstance( degree , int )
|
|
373
|
+
assert isinstance( periodic, bool )
|
|
374
|
+
|
|
375
|
+
# Consistency checks
|
|
376
|
+
assert len(breaks) > 1
|
|
377
|
+
assert all( np.diff(breaks) > 0 )
|
|
378
|
+
assert degree > 0
|
|
379
|
+
assert 1 <= multiplicity and multiplicity <= degree + 1
|
|
380
|
+
|
|
381
|
+
if periodic:
|
|
382
|
+
assert len(breaks) > degree
|
|
383
|
+
|
|
384
|
+
T = np.zeros(multiplicity * len(breaks[1:-1]) + 2 + 2 * degree)
|
|
385
|
+
ncells = len(breaks) - 1
|
|
386
|
+
|
|
387
|
+
for i in range(0, ncells+1):
|
|
388
|
+
T[degree + 1 + (i-1) * multiplicity :degree + 1 + i * multiplicity ] = breaks[i]
|
|
389
|
+
|
|
390
|
+
len_out = len(T)
|
|
391
|
+
|
|
392
|
+
if periodic:
|
|
393
|
+
period = breaks[-1]-breaks[0]
|
|
394
|
+
|
|
395
|
+
T[: degree + 1 - multiplicity] = T[len_out - 2 * (degree + 1 )+ multiplicity: len_out - (degree + 1)] - period
|
|
396
|
+
T[len_out - (degree + 1 - multiplicity) :] = T[degree + 1:2*(degree + 1)- multiplicity] + period
|
|
397
|
+
|
|
398
|
+
else:
|
|
399
|
+
T[0:degree + 1 - multiplicity] = breaks[0]
|
|
400
|
+
T[len_out - degree - 1 + multiplicity:] = breaks[-1]
|
|
401
|
+
|
|
402
|
+
return T
|
|
403
|
+
|
|
404
|
+
#==============================================================================
|
|
405
|
+
def elevate_knots_true(knots, degree, periodic, multiplicity=1, tol=1e-15):
|
|
406
|
+
knots = np.array(knots)
|
|
407
|
+
|
|
408
|
+
if periodic:
|
|
409
|
+
T, p = knots, degree
|
|
410
|
+
period = T[len(knots) -1 - p] - T[p]
|
|
411
|
+
left = [T[len(knots) -2 - 2 * p + multiplicity-1] - period]
|
|
412
|
+
right = [T[2 * p + 2 - multiplicity] + period]
|
|
413
|
+
else:
|
|
414
|
+
left = [knots[0],*knots[:degree+1]]
|
|
415
|
+
right = [knots[-1],*knots[-degree-1:]]
|
|
416
|
+
|
|
417
|
+
diff = np.append(True, np.diff(knots[degree+1:-degree-1])>tol)
|
|
418
|
+
if len(knots[degree+1:-degree-1])>0:
|
|
419
|
+
unique = knots[degree+1:-degree-1][diff]
|
|
420
|
+
knots = np.repeat(unique, multiplicity)
|
|
421
|
+
else:
|
|
422
|
+
knots = knots[degree+1:-degree-1]
|
|
423
|
+
|
|
424
|
+
return np.array([*left, *knots, *right])
|
|
425
|
+
|
|
426
|
+
#==============================================================================
|
|
427
|
+
def quadrature_grid_true(breaks, quad_rule_x, quad_rule_w):
|
|
428
|
+
# Check that input arrays have correct size
|
|
429
|
+
assert len(breaks) >= 2
|
|
430
|
+
assert len(quad_rule_x) == len(quad_rule_w)
|
|
431
|
+
|
|
432
|
+
# Check that provided quadrature rule is defined on interval [-1, 1]
|
|
433
|
+
assert min(quad_rule_x) >= -1
|
|
434
|
+
assert max(quad_rule_x) <= +1
|
|
435
|
+
|
|
436
|
+
quad_rule_x = np.asarray(quad_rule_x)
|
|
437
|
+
quad_rule_w = np.asarray(quad_rule_w)
|
|
438
|
+
|
|
439
|
+
ne = len(breaks) - 1
|
|
440
|
+
nq = len(quad_rule_x)
|
|
441
|
+
quad_x = np.zeros((ne, nq))
|
|
442
|
+
quad_w = np.zeros((ne, nq))
|
|
443
|
+
|
|
444
|
+
# Compute location and weight of quadrature points from basic rule
|
|
445
|
+
for ie, (a, b) in enumerate(zip(breaks[:-1], breaks[1:])):
|
|
446
|
+
c0 = 0.5 * (a + b)
|
|
447
|
+
c1 = 0.5 * (b - a)
|
|
448
|
+
quad_x[ie, :] = c1 * quad_rule_x[:] + c0
|
|
449
|
+
quad_w[ie, :] = c1 * quad_rule_w[:]
|
|
450
|
+
|
|
451
|
+
return quad_x, quad_w
|
|
452
|
+
|
|
453
|
+
#==============================================================================
|
|
454
|
+
def basis_ders_on_quad_grid_true(knots, degree, quad_grid, nders, normalization):
|
|
455
|
+
ne,nq = quad_grid.shape
|
|
456
|
+
basis = np.zeros((ne, degree+1, nders+1, nq))
|
|
457
|
+
|
|
458
|
+
if normalization == 'M':
|
|
459
|
+
scaling = 1. / basis_integrals_true(knots, degree)
|
|
460
|
+
|
|
461
|
+
for ie in range(ne):
|
|
462
|
+
xx = quad_grid[ie, :]
|
|
463
|
+
for iq, xq in enumerate(xx):
|
|
464
|
+
span = find_span_true(knots, degree, xq)
|
|
465
|
+
ders = basis_funs_all_ders_true(knots, degree, xq, span, nders)
|
|
466
|
+
if normalization == 'M':
|
|
467
|
+
ders *= scaling[None, span-degree:span+1]
|
|
468
|
+
basis[ie, :, :, iq] = ders.transpose()
|
|
469
|
+
|
|
470
|
+
return basis
|
|
471
|
+
|
|
472
|
+
#==============================================================================
|
|
473
|
+
def basis_integrals_true(knots, degree):
|
|
474
|
+
T = knots
|
|
475
|
+
p = degree
|
|
476
|
+
n = len(T)-p-1
|
|
477
|
+
K = np.array([(T[i+p+1] - T[i]) / (p + 1) for i in range(n)])
|
|
478
|
+
|
|
479
|
+
return K
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
###############################################################################
|
|
483
|
+
# Tests
|
|
484
|
+
###############################################################################
|
|
485
|
+
# Tolerance for testing float equality
|
|
486
|
+
RTOL = 1e-11
|
|
487
|
+
ATOL = 1e-11
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
491
|
+
[(np.sort(np.random.random(15)), 2),
|
|
492
|
+
(np.sort(np.random.random(15)), 3),
|
|
493
|
+
(np.sort(np.random.random(15)), 4),
|
|
494
|
+
(np.sort(np.random.random(15)), 5),
|
|
495
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
496
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
497
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
498
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
499
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
500
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
501
|
+
@pytest.mark.parametrize('x', (np.random.random(), np.random.random(), np.random.random()))
|
|
502
|
+
|
|
503
|
+
def test_find_span(knots, degree, x):
|
|
504
|
+
expected = find_span_true(knots, degree, x)
|
|
505
|
+
out = find_span(knots, degree, x)
|
|
506
|
+
|
|
507
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
511
|
+
[(np.sort(np.random.random(15)), 2),
|
|
512
|
+
(np.sort(np.random.random(15)), 3),
|
|
513
|
+
(np.sort(np.random.random(15)), 4),
|
|
514
|
+
(np.sort(np.random.random(15)), 5),
|
|
515
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
516
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
517
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
518
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
519
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
520
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
521
|
+
@pytest.mark.parametrize('x', (np.random.random(), np.random.random(), np.random.random()))
|
|
522
|
+
def test_basis_funs(knots, degree, x):
|
|
523
|
+
span = find_span(knots, degree, x)
|
|
524
|
+
expected = basis_funs_true(knots, degree, x, span)
|
|
525
|
+
out = basis_funs(knots, degree, x, span)
|
|
526
|
+
|
|
527
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
531
|
+
[(np.sort(np.random.random(15)), 2),
|
|
532
|
+
(np.sort(np.random.random(15)), 3),
|
|
533
|
+
(np.sort(np.random.random(15)), 4),
|
|
534
|
+
(np.sort(np.random.random(15)), 5),
|
|
535
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
536
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
537
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
538
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
539
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
540
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
541
|
+
@pytest.mark.parametrize('x', (np.random.random(), np.random.random(), np.random.random()))
|
|
542
|
+
def test_basis_funs_1st_der(knots, degree, x):
|
|
543
|
+
span = find_span(knots, degree, x)
|
|
544
|
+
expected = basis_funs_1st_der_true(knots, degree, x, span)
|
|
545
|
+
out = basis_funs_1st_der(knots, degree, x, span)
|
|
546
|
+
|
|
547
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
551
|
+
[(np.sort(np.random.random(15)), 2),
|
|
552
|
+
(np.sort(np.random.random(15)), 3),
|
|
553
|
+
(np.sort(np.random.random(15)), 4),
|
|
554
|
+
(np.sort(np.random.random(15)), 5),
|
|
555
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
556
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
557
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
558
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
559
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
560
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
561
|
+
@pytest.mark.parametrize('x', (np.random.random(), np.random.random(), np.random.random()))
|
|
562
|
+
@pytest.mark.parametrize('n', (2, 3, 4, 5))
|
|
563
|
+
@pytest.mark.parametrize('normalization', ('B', 'M'))
|
|
564
|
+
def test_basis_funs_all_ders(knots, degree, x, n, normalization):
|
|
565
|
+
span = find_span(knots, degree, x)
|
|
566
|
+
expected = basis_funs_all_ders_true(knots, degree, x, span, n, normalization)
|
|
567
|
+
out = basis_funs_all_ders(knots, degree, x, span, n, normalization)
|
|
568
|
+
|
|
569
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
573
|
+
[(np.sort(np.random.random(15)), 2),
|
|
574
|
+
(np.sort(np.random.random(15)), 3),
|
|
575
|
+
(np.sort(np.random.random(15)), 4),
|
|
576
|
+
(np.sort(np.random.random(15)), 5),
|
|
577
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
578
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
579
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
580
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
581
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
582
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
583
|
+
@pytest.mark.parametrize('periodic', (True, False))
|
|
584
|
+
@pytest.mark.parametrize('normalization', ('B', 'M'))
|
|
585
|
+
@pytest.mark.parametrize('xgrid', (np.random.random(10), np.random.random(15)))
|
|
586
|
+
def test_collocation_matrix(knots, degree, periodic, normalization, xgrid):
|
|
587
|
+
expected = collocation_matrix_true(knots, degree, periodic, normalization, xgrid)
|
|
588
|
+
out = collocation_matrix(knots, degree, periodic, normalization, xgrid)
|
|
589
|
+
|
|
590
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
594
|
+
[(np.sort(np.random.random(15)), 2),
|
|
595
|
+
(np.sort(np.random.random(15)), 3),
|
|
596
|
+
(np.sort(np.random.random(15)), 4),
|
|
597
|
+
(np.sort(np.random.random(15)), 5),
|
|
598
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
599
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
600
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
601
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
602
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
603
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
604
|
+
@pytest.mark.parametrize('periodic', [True, False])
|
|
605
|
+
@pytest.mark.parametrize('normalization', ('B', 'M'))
|
|
606
|
+
@pytest.mark.parametrize('xgrid', (np.random.random(10), np.random.random(15)))
|
|
607
|
+
def test_histopolation_matrix(knots, degree, periodic, normalization, xgrid):
|
|
608
|
+
xgrid = np.sort(np.unique(xgrid))
|
|
609
|
+
expected = histopolation_matrix_true(knots, degree, periodic, normalization, xgrid)
|
|
610
|
+
out = histopolation_matrix(knots, degree, periodic, normalization, xgrid)
|
|
611
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
615
|
+
[(np.sort(np.random.random(15)), 2),
|
|
616
|
+
(np.sort(np.random.random(15)), 3),
|
|
617
|
+
(np.sort(np.random.random(15)), 4),
|
|
618
|
+
(np.sort(np.random.random(15)), 5),
|
|
619
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
620
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
621
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
622
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
623
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
624
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
625
|
+
def test_breakpoints(knots, degree):
|
|
626
|
+
expected = breakpoints_true(knots, degree)
|
|
627
|
+
out = breakpoints(knots, degree)
|
|
628
|
+
|
|
629
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
630
|
+
|
|
631
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
632
|
+
[(np.sort(np.random.random(15)), 2),
|
|
633
|
+
(np.sort(np.random.random(15)), 3),
|
|
634
|
+
(np.sort(np.random.random(15)), 4),
|
|
635
|
+
(np.sort(np.random.random(15)), 5),
|
|
636
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
637
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
638
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
639
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
640
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
641
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
642
|
+
@pytest.mark.parametrize('periodic', [True, False])
|
|
643
|
+
def test_greville(knots, degree, periodic):
|
|
644
|
+
expected = greville_true(knots, degree, periodic)
|
|
645
|
+
out = greville(knots, degree, periodic)
|
|
646
|
+
|
|
647
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
651
|
+
[(np.sort(np.random.random(15)), 2),
|
|
652
|
+
(np.sort(np.random.random(15)), 3),
|
|
653
|
+
(np.sort(np.random.random(15)), 4),
|
|
654
|
+
(np.sort(np.random.random(15)), 5),
|
|
655
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
656
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
657
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
658
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
659
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
660
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
661
|
+
def test_elements_spans(knots, degree):
|
|
662
|
+
expected = elements_spans_true(knots, degree)
|
|
663
|
+
out = elements_spans(knots, degree)
|
|
664
|
+
|
|
665
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
666
|
+
|
|
667
|
+
@pytest.mark.parametrize('breaks', (np.linspace(0, 1, 10, endpoint=False),
|
|
668
|
+
np.sort(np.random.random(15))))
|
|
669
|
+
@pytest.mark.parametrize(('degree', 'multiplicity'), [(2, 1),
|
|
670
|
+
(3, 1), (3, 2),
|
|
671
|
+
(4, 1), (4, 2), (4, 3),
|
|
672
|
+
(5, 1), (5, 2), (5, 3), (5, 4)])
|
|
673
|
+
@pytest.mark.parametrize('periodic', (True, False))
|
|
674
|
+
def test_make_knots(breaks, degree, periodic, multiplicity):
|
|
675
|
+
expected = make_knots_true(breaks, degree, periodic, multiplicity)
|
|
676
|
+
out = make_knots(breaks, degree, periodic, multiplicity)
|
|
677
|
+
print(out, expected)
|
|
678
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
682
|
+
[(np.sort(np.random.random(15)), 2),
|
|
683
|
+
(np.sort(np.random.random(15)), 3),
|
|
684
|
+
(np.sort(np.random.random(15)), 4),
|
|
685
|
+
(np.sort(np.random.random(15)), 5),
|
|
686
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
687
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
688
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
689
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
690
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
691
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
692
|
+
@pytest.mark.parametrize('periodic', (True, False))
|
|
693
|
+
@pytest.mark.parametrize('multiplicity', (1, 2, 3))
|
|
694
|
+
def test_elevate_knots(knots, degree, periodic, multiplicity):
|
|
695
|
+
expected = elevate_knots_true(knots, degree, periodic, multiplicity)
|
|
696
|
+
out = elevate_knots(knots, degree, periodic, multiplicity)
|
|
697
|
+
|
|
698
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
@pytest.mark.parametrize('breaks', (np.linspace(0, 1, 10, endpoint=False),
|
|
702
|
+
np.sort(np.random.random(15))))
|
|
703
|
+
@pytest.mark.parametrize('nquads', (2, 3, 4, 5))
|
|
704
|
+
def test_quadrature_grid(breaks, nquads):
|
|
705
|
+
quad_x, quad_w = gauss_legendre(nquads)
|
|
706
|
+
expected = quadrature_grid_true(breaks, quad_x, quad_w)
|
|
707
|
+
out = quadrature_grid(breaks, quad_x, quad_w)
|
|
708
|
+
|
|
709
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
713
|
+
[(np.sort(np.random.random(15)), 2),
|
|
714
|
+
(np.sort(np.random.random(15)), 3),
|
|
715
|
+
(np.sort(np.random.random(15)), 4),
|
|
716
|
+
(np.sort(np.random.random(15)), 5),
|
|
717
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
718
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
719
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
720
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
721
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
722
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
723
|
+
@pytest.mark.parametrize('n', (2, 3, 4, 5))
|
|
724
|
+
@pytest.mark.parametrize('normalization', ('B', 'M'))
|
|
725
|
+
@pytest.mark.parametrize('nquads', (2, 3, 4, 5))
|
|
726
|
+
def test_basis_ders_on_quad_grid(knots, degree, n, normalization, nquads):
|
|
727
|
+
quad_rule_x, quad_rule_w = gauss_legendre(nquads)
|
|
728
|
+
breaks = breakpoints_true(knots, degree)
|
|
729
|
+
quad_grid, quad_weights = quadrature_grid_true(breaks, quad_rule_x, quad_rule_w)
|
|
730
|
+
|
|
731
|
+
expected = basis_ders_on_quad_grid_true(knots, degree, quad_grid, n, normalization)
|
|
732
|
+
out = basis_ders_on_quad_grid(knots, degree, quad_grid, n, normalization)
|
|
733
|
+
|
|
734
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
@pytest.mark.parametrize(('knots', 'degree'),
|
|
738
|
+
[(np.sort(np.random.random(15)), 2),
|
|
739
|
+
(np.sort(np.random.random(15)), 3),
|
|
740
|
+
(np.sort(np.random.random(15)), 4),
|
|
741
|
+
(np.sort(np.random.random(15)), 5),
|
|
742
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 2),
|
|
743
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 3),
|
|
744
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 4),
|
|
745
|
+
(np.array([0.0, 0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0, 1.0]), 5),
|
|
746
|
+
(np.array([0.0, 0.0, 0.0, 1.0, 1.0, 1.0]), 2),
|
|
747
|
+
(np.array([0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]), 3)])
|
|
748
|
+
def test_basis_integrals(knots, degree):
|
|
749
|
+
expected = basis_integrals_true(knots, degree)
|
|
750
|
+
out = basis_integrals(knots, degree)
|
|
751
|
+
|
|
752
|
+
assert np.allclose(expected, out, atol=ATOL, rtol=RTOL)
|