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
feectools/fem/splines.py
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# Copyright 2018 Ahmed Ratnani, Yaman Güçlü
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from scipy.sparse import csc_matrix, csr_matrix, dia_matrix
|
|
6
|
+
|
|
7
|
+
from feectools.linalg.stencil import StencilVectorSpace
|
|
8
|
+
from feectools.linalg.direct_solvers import BandedSolver, SparseSolver
|
|
9
|
+
from feectools.fem.basic import FemSpace, FemField
|
|
10
|
+
from feectools.core.bsplines import (
|
|
11
|
+
find_span,
|
|
12
|
+
basis_funs,
|
|
13
|
+
collocation_matrix,
|
|
14
|
+
histopolation_matrix,
|
|
15
|
+
breakpoints,
|
|
16
|
+
greville,
|
|
17
|
+
make_knots,
|
|
18
|
+
elevate_knots,
|
|
19
|
+
basis_integrals,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from feectools.utilities.utils import unroll_edges, refine_array_1d
|
|
23
|
+
from feectools.ddm.cart import DomainDecomposition, CartDecomposition
|
|
24
|
+
|
|
25
|
+
__all__ = ('SplineSpace',)
|
|
26
|
+
|
|
27
|
+
#===============================================================================
|
|
28
|
+
class SplineSpace( FemSpace ):
|
|
29
|
+
"""
|
|
30
|
+
a 1D Splines Finite Element space
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
degree : int
|
|
35
|
+
Polynomial degree.
|
|
36
|
+
|
|
37
|
+
knots : array_like
|
|
38
|
+
Coordinates of knots (clamped or extended by periodicity).
|
|
39
|
+
|
|
40
|
+
grid: array_like
|
|
41
|
+
Coordinates of the grid. Used to construct the knots sequence, if not given.
|
|
42
|
+
|
|
43
|
+
multiplicity: int
|
|
44
|
+
Multiplicity of the knots in the knot sequence.
|
|
45
|
+
|
|
46
|
+
parent_multiplicity: int
|
|
47
|
+
Multiplicity of the parent knot sequence, if the space is reduced space.
|
|
48
|
+
|
|
49
|
+
periodic : bool
|
|
50
|
+
True if domain is periodic, False otherwise.
|
|
51
|
+
Default: False
|
|
52
|
+
|
|
53
|
+
dirichlet : tuple, list
|
|
54
|
+
True if using homogeneous dirichlet boundary conditions, False
|
|
55
|
+
otherwise. Must be specified for each bound
|
|
56
|
+
Default: (False, False)
|
|
57
|
+
|
|
58
|
+
basis : str
|
|
59
|
+
Set to "B" for B-splines (have partition of unity)
|
|
60
|
+
Set to "M" for M-splines (have unit integrals)
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
def __init__(self, degree, knots=None, grid=None, multiplicity=None, parent_multiplicity=None,
|
|
64
|
+
periodic=False, dirichlet=(False, False), basis='B', pads=None):
|
|
65
|
+
|
|
66
|
+
if basis not in ['B', 'M']:
|
|
67
|
+
raise ValueError(" only options for basis functions are B or M ")
|
|
68
|
+
|
|
69
|
+
if (knots is not None) and (grid is not None):
|
|
70
|
+
raise ValueError( 'Cannot provide both grid and knots.' )
|
|
71
|
+
|
|
72
|
+
if (knots is None) and (grid is None):
|
|
73
|
+
raise ValueError('Either knots or grid must be provided.')
|
|
74
|
+
|
|
75
|
+
if (knots is not None) and (multiplicity is not None):
|
|
76
|
+
raise ValueError( 'Cannot provide both knots and multiplicity.' )
|
|
77
|
+
|
|
78
|
+
if (multiplicity is not None) and multiplicity<1:
|
|
79
|
+
raise ValueError('multiplicity should be >=1')
|
|
80
|
+
|
|
81
|
+
if (parent_multiplicity is not None) and parent_multiplicity<1:
|
|
82
|
+
raise ValueError('parent_multiplicity should be >=1')
|
|
83
|
+
|
|
84
|
+
if knots is None:
|
|
85
|
+
if multiplicity is None:multiplicity = 1
|
|
86
|
+
knots = make_knots( grid, degree, periodic, multiplicity )
|
|
87
|
+
|
|
88
|
+
if grid is None:
|
|
89
|
+
grid = breakpoints(knots, degree)
|
|
90
|
+
|
|
91
|
+
indices = np.where(np.diff(knots[degree:len(knots)-degree])>1e-15)[0]
|
|
92
|
+
|
|
93
|
+
if len(indices)>0:
|
|
94
|
+
multiplicity = np.diff(indices).max(initial=1)
|
|
95
|
+
else:
|
|
96
|
+
multiplicity = max(1,len(knots[degree+1:-degree-1]))
|
|
97
|
+
|
|
98
|
+
if parent_multiplicity is None:
|
|
99
|
+
parent_multiplicity = multiplicity
|
|
100
|
+
|
|
101
|
+
assert parent_multiplicity >= multiplicity
|
|
102
|
+
|
|
103
|
+
# TODO: verify that user-provided knots make sense in periodic case
|
|
104
|
+
|
|
105
|
+
# Number of basis function in space (= cardinality)
|
|
106
|
+
if periodic:
|
|
107
|
+
nbasis = len(knots) - 2*degree - 2 + multiplicity
|
|
108
|
+
else:
|
|
109
|
+
defect = 0
|
|
110
|
+
if dirichlet[0]: defect += 1
|
|
111
|
+
if dirichlet[1]: defect += 1
|
|
112
|
+
nbasis = len(knots) - degree - 1 - defect
|
|
113
|
+
|
|
114
|
+
# Coefficients to convert B-splines to M-splines (if needed)
|
|
115
|
+
if basis == 'M':
|
|
116
|
+
scaling_array = 1 / basis_integrals(knots, degree)
|
|
117
|
+
else:
|
|
118
|
+
scaling_array = None
|
|
119
|
+
|
|
120
|
+
# Store attributes in object
|
|
121
|
+
self._degree = degree
|
|
122
|
+
self._pads = pads or degree
|
|
123
|
+
self._knots = knots
|
|
124
|
+
self._periodic = periodic # this is a scalar bool
|
|
125
|
+
self._multiplicity = multiplicity
|
|
126
|
+
self._dirichlet = dirichlet
|
|
127
|
+
self._basis = basis
|
|
128
|
+
self._nbasis = nbasis
|
|
129
|
+
self._breaks = grid
|
|
130
|
+
self._ncells = len(grid) - 1
|
|
131
|
+
self._greville = greville(knots, degree, periodic, multiplicity = multiplicity)
|
|
132
|
+
self._ext_greville = greville(elevate_knots(knots, degree, periodic, multiplicity=multiplicity), degree+1, periodic, multiplicity = multiplicity)
|
|
133
|
+
self._scaling_array = scaling_array
|
|
134
|
+
self._parent_multiplicity = parent_multiplicity
|
|
135
|
+
self._histopolation_grid = unroll_edges(self.domain, self.ext_greville)
|
|
136
|
+
|
|
137
|
+
# Create space of spline coefficients
|
|
138
|
+
domain_decomposition = DomainDecomposition([self._ncells], [periodic])
|
|
139
|
+
cart = CartDecomposition(domain_decomposition, [nbasis], [np.array([0])],[np.array([nbasis-1])], [self._pads], [multiplicity])
|
|
140
|
+
self._coeff_space = StencilVectorSpace(cart)
|
|
141
|
+
|
|
142
|
+
# Store flag: object NOT YET prepared for interpolation / histopolation
|
|
143
|
+
self._interpolation_ready = False
|
|
144
|
+
self._histopolation_ready = False
|
|
145
|
+
|
|
146
|
+
self._symbolic_space = None
|
|
147
|
+
# ...
|
|
148
|
+
|
|
149
|
+
# ...
|
|
150
|
+
@property
|
|
151
|
+
def histopolation_grid(self):
|
|
152
|
+
"""
|
|
153
|
+
Coordinates of the N+1 points x[i] that define the N 1D edges
|
|
154
|
+
(x[i], x[i+1]) for histopolation, where N is equal to the number of
|
|
155
|
+
basis functions (i.e. the cardinality of the space).
|
|
156
|
+
|
|
157
|
+
In the non-periodic case x is simply the array of extended Greville
|
|
158
|
+
points. In the periodic case we "unroll" the 1D edges to ensure that
|
|
159
|
+
they correspond to positive, well-defined intervals with x[i] < x[i+1].
|
|
160
|
+
|
|
161
|
+
"""
|
|
162
|
+
return self._histopolation_grid
|
|
163
|
+
|
|
164
|
+
# ...
|
|
165
|
+
def init_interpolation( self, dtype=float ):
|
|
166
|
+
"""
|
|
167
|
+
Compute the 1D collocation matrix and factorize it, in preparation
|
|
168
|
+
for the calculation of a spline interpolant given the values at the
|
|
169
|
+
Greville points.
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
if self.greville.size == 1:
|
|
173
|
+
imat = np.ones((1, 1), dtype=float)
|
|
174
|
+
else:
|
|
175
|
+
imat = collocation_matrix(
|
|
176
|
+
knots = self.knots,
|
|
177
|
+
degree = self.degree,
|
|
178
|
+
periodic = self.periodic,
|
|
179
|
+
normalization = self.basis,
|
|
180
|
+
xgrid = self.greville,
|
|
181
|
+
multiplicity = self.multiplicity
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
if self.periodic:
|
|
185
|
+
# Convert to CSC format and compute sparse LU decomposition
|
|
186
|
+
self._interpolator = SparseSolver( csc_matrix( imat ) )
|
|
187
|
+
else:
|
|
188
|
+
# Convert to LAPACK banded format (see DGBTRF function)
|
|
189
|
+
dmat = dia_matrix( imat )
|
|
190
|
+
l = abs( dmat.offsets.min() )
|
|
191
|
+
u = dmat.offsets.max()
|
|
192
|
+
cmat = csr_matrix( dmat )
|
|
193
|
+
bmat = np.zeros( (1+u+2*l, cmat.shape[1]), dtype=dtype )
|
|
194
|
+
for i,j in zip( *cmat.nonzero() ):
|
|
195
|
+
bmat[u+l+i-j,j] = cmat[i,j]
|
|
196
|
+
self._interpolator = BandedSolver( u, l, bmat )
|
|
197
|
+
self.imat = imat
|
|
198
|
+
|
|
199
|
+
# Store flag
|
|
200
|
+
self._interpolation_ready = True
|
|
201
|
+
|
|
202
|
+
# ...
|
|
203
|
+
def init_histopolation( self, dtype=float):
|
|
204
|
+
"""
|
|
205
|
+
Compute the 1D histopolation matrix and factorize it, in preparation
|
|
206
|
+
for the calculation of a spline interpolant given the integrals within
|
|
207
|
+
the cells defined by the extended Greville points.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
imat = histopolation_matrix(
|
|
211
|
+
knots = self.knots,
|
|
212
|
+
degree = self.degree,
|
|
213
|
+
periodic = self.periodic,
|
|
214
|
+
normalization = self.basis,
|
|
215
|
+
xgrid = self.ext_greville,
|
|
216
|
+
multiplicity = self._multiplicity
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
self.hmat= imat
|
|
220
|
+
if self.periodic:
|
|
221
|
+
# Convert to CSC format and compute sparse LU decomposition
|
|
222
|
+
self._histopolator = SparseSolver( csc_matrix( imat ) )
|
|
223
|
+
else:
|
|
224
|
+
# Convert to LAPACK banded format (see DGBTRF function)
|
|
225
|
+
dmat = dia_matrix( imat )
|
|
226
|
+
l = abs( dmat.offsets.min() )
|
|
227
|
+
u = dmat.offsets.max()
|
|
228
|
+
cmat = csr_matrix( dmat )
|
|
229
|
+
bmat = np.zeros( (1+u+2*l, cmat.shape[1]), dtype=dtype)
|
|
230
|
+
for i,j in zip( *cmat.nonzero() ):
|
|
231
|
+
bmat[u+l+i-j,j] = cmat[i,j]
|
|
232
|
+
self._histopolator = BandedSolver( u, l, bmat )
|
|
233
|
+
|
|
234
|
+
# Store flag
|
|
235
|
+
self._histopolation_ready = True
|
|
236
|
+
|
|
237
|
+
#--------------------------------------------------------------------------
|
|
238
|
+
# Abstract interface: read-only attributes
|
|
239
|
+
#--------------------------------------------------------------------------
|
|
240
|
+
@property
|
|
241
|
+
def ldim( self ):
|
|
242
|
+
""" Parametric dimension.
|
|
243
|
+
"""
|
|
244
|
+
return 1
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def periodic( self ):
|
|
248
|
+
""" True if domain is periodic, False otherwise.
|
|
249
|
+
"""
|
|
250
|
+
# [YG, 28.03.2025]: according to the abstract interface of FemSpace,
|
|
251
|
+
# this property should return a tuple of `ldim` booleans. Instead, this
|
|
252
|
+
# property returns a single boolean.
|
|
253
|
+
return self._periodic
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def pads( self ):
|
|
257
|
+
""" Padding for potential parallel assembly.
|
|
258
|
+
"""
|
|
259
|
+
return self._pads
|
|
260
|
+
|
|
261
|
+
@property
|
|
262
|
+
def mapping( self ):
|
|
263
|
+
""" Assume identity mapping for now.
|
|
264
|
+
"""
|
|
265
|
+
# [YG, 28.03.2025]: not clear why there should be no mapping here...
|
|
266
|
+
# Clearly this property is never used in feectools.
|
|
267
|
+
return None
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def coeff_space( self ):
|
|
271
|
+
"""Returns the topological associated vector space."""
|
|
272
|
+
return self._coeff_space
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def symbolic_space( self ):
|
|
276
|
+
return self._symbolic_space
|
|
277
|
+
|
|
278
|
+
@symbolic_space.setter
|
|
279
|
+
def symbolic_space( self, symbolic_space ):
|
|
280
|
+
#assert isinstance(symbolic_space, BasicFunctionSpace)
|
|
281
|
+
self._symbolic_space = symbolic_space
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def is_multipatch(self):
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def is_vector_valued(self):
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def patch_spaces(self):
|
|
293
|
+
return (self,)
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def component_spaces(self):
|
|
297
|
+
return (self,)
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def axis_spaces(self):
|
|
301
|
+
return (self,)
|
|
302
|
+
|
|
303
|
+
#--------------------------------------------------------------------------
|
|
304
|
+
# Abstract interface: evaluation methods
|
|
305
|
+
#--------------------------------------------------------------------------
|
|
306
|
+
def eval_field(self, field, *eta , weights=None):
|
|
307
|
+
assert isinstance( field, FemField )
|
|
308
|
+
assert field.space is self
|
|
309
|
+
assert len(eta) == 1
|
|
310
|
+
|
|
311
|
+
eta = eta[0]
|
|
312
|
+
|
|
313
|
+
span = find_span( self.knots, self.degree, eta)
|
|
314
|
+
|
|
315
|
+
basis_array = basis_funs( self.knots, self.degree, eta, span)
|
|
316
|
+
index = slice(span-self.degree, span + 1)
|
|
317
|
+
|
|
318
|
+
if self.basis == 'M':
|
|
319
|
+
basis_array *= self._scaling_array[index]
|
|
320
|
+
|
|
321
|
+
coeffs = field.coeffs[index].copy()
|
|
322
|
+
|
|
323
|
+
if weights:
|
|
324
|
+
coeffs *= weights[index]
|
|
325
|
+
|
|
326
|
+
return np.dot(coeffs,basis_array)
|
|
327
|
+
|
|
328
|
+
# ...
|
|
329
|
+
def eval_field_gradient( self, field, *eta , weights=None):
|
|
330
|
+
|
|
331
|
+
assert isinstance( field, FemField )
|
|
332
|
+
assert field.space is self
|
|
333
|
+
assert len( eta ) == 1
|
|
334
|
+
|
|
335
|
+
raise NotImplementedError()
|
|
336
|
+
|
|
337
|
+
#--------------------------------------------------------------------------
|
|
338
|
+
# Other properties
|
|
339
|
+
#--------------------------------------------------------------------------
|
|
340
|
+
@property
|
|
341
|
+
def basis( self ):
|
|
342
|
+
return self._basis
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def interpolation_grid( self ):
|
|
346
|
+
if self.basis == 'B':
|
|
347
|
+
return self.greville
|
|
348
|
+
elif self.basis == 'M':
|
|
349
|
+
return self.ext_greville
|
|
350
|
+
else:
|
|
351
|
+
raise NotImplementedError()
|
|
352
|
+
|
|
353
|
+
@property
|
|
354
|
+
def nbasis( self ):
|
|
355
|
+
""" Number of basis functions, i.e. cardinality of spline space.
|
|
356
|
+
"""
|
|
357
|
+
return self._nbasis
|
|
358
|
+
|
|
359
|
+
@property
|
|
360
|
+
def degree( self ):
|
|
361
|
+
""" Spline degree.
|
|
362
|
+
"""
|
|
363
|
+
return self._degree
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def ncells( self ):
|
|
367
|
+
""" Number of cells in domain.
|
|
368
|
+
"""
|
|
369
|
+
return self._ncells
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def dirichlet( self ):
|
|
373
|
+
""" True if using homogeneous dirichlet boundary conditions, False otherwise.
|
|
374
|
+
"""
|
|
375
|
+
return self._dirichlet
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def knots( self ):
|
|
379
|
+
""" Knot sequence.
|
|
380
|
+
"""
|
|
381
|
+
return self._knots
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def multiplicity( self ):
|
|
385
|
+
return self._multiplicity
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def parent_multiplicity( self ):
|
|
389
|
+
return self._parent_multiplicity
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def breaks( self ):
|
|
393
|
+
""" List of breakpoints.
|
|
394
|
+
"""
|
|
395
|
+
return self._breaks
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def domain( self ):
|
|
399
|
+
""" Domain boundaries [a,b].
|
|
400
|
+
"""
|
|
401
|
+
breaks = self.breaks
|
|
402
|
+
return breaks[0], breaks[-1]
|
|
403
|
+
|
|
404
|
+
@property
|
|
405
|
+
def greville( self ):
|
|
406
|
+
""" Coordinates of all Greville points. Used for interpolation.
|
|
407
|
+
"""
|
|
408
|
+
return self._greville
|
|
409
|
+
|
|
410
|
+
@property
|
|
411
|
+
def ext_greville( self ):
|
|
412
|
+
""" Greville coordinates of 'extended' space with degree p+1.
|
|
413
|
+
Used for histopolation.
|
|
414
|
+
"""
|
|
415
|
+
return self._ext_greville
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def scaling_array(self):
|
|
419
|
+
"""
|
|
420
|
+
If self.basis=='M', return array used to rescale B-splines to M-splines
|
|
421
|
+
If self.basis=='B', return None.
|
|
422
|
+
|
|
423
|
+
The length of the scaling array is (len(knots)-degree-1).
|
|
424
|
+
"""
|
|
425
|
+
return self._scaling_array
|
|
426
|
+
|
|
427
|
+
#--------------------------------------------------------------------------
|
|
428
|
+
# Other methods
|
|
429
|
+
#--------------------------------------------------------------------------
|
|
430
|
+
def compute_interpolant( self, values, field ):
|
|
431
|
+
"""
|
|
432
|
+
Compute field (i.e. update its spline coefficients) such that it
|
|
433
|
+
interpolates a certain function $f(x)$ at the Greville points.
|
|
434
|
+
|
|
435
|
+
Parameters
|
|
436
|
+
----------
|
|
437
|
+
values : array_like (nbasis,)
|
|
438
|
+
Function values $f(x_i)$ at the 'nbasis' Greville points $x_i$,
|
|
439
|
+
to be interpolated.
|
|
440
|
+
|
|
441
|
+
field : FemField
|
|
442
|
+
Input/output argument: spline that has to interpolate the given
|
|
443
|
+
values.
|
|
444
|
+
|
|
445
|
+
"""
|
|
446
|
+
assert len( values ) == self.nbasis
|
|
447
|
+
assert isinstance( field, FemField )
|
|
448
|
+
assert field.space is self
|
|
449
|
+
|
|
450
|
+
if not self._interpolation_ready:
|
|
451
|
+
self.init_interpolation()
|
|
452
|
+
|
|
453
|
+
n = self.nbasis
|
|
454
|
+
c = field.coeffs
|
|
455
|
+
|
|
456
|
+
c[0:n] = self._interpolator.solve( values )
|
|
457
|
+
c.update_ghost_regions()
|
|
458
|
+
|
|
459
|
+
# ...
|
|
460
|
+
def compute_histopolant( self, values, field ):
|
|
461
|
+
"""
|
|
462
|
+
Compute field (i.e. update its spline coefficients) such that its
|
|
463
|
+
integrals between the extended Greville points match the given
|
|
464
|
+
values.
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
values : array_like (nbasis,)
|
|
469
|
+
Integral values between the 'nbasis' extended Greville cells
|
|
470
|
+
$[x_i, x_{i+1}]$, to be matched by the spline.
|
|
471
|
+
|
|
472
|
+
field : FemField
|
|
473
|
+
Input/output argument: spline that has to match the given
|
|
474
|
+
integral values.
|
|
475
|
+
|
|
476
|
+
"""
|
|
477
|
+
assert len( values ) == self.nbasis
|
|
478
|
+
assert isinstance( field, FemField )
|
|
479
|
+
assert field.space is self
|
|
480
|
+
|
|
481
|
+
if not self._histopolation_ready:
|
|
482
|
+
self.init_histopolation()
|
|
483
|
+
|
|
484
|
+
n = self.nbasis
|
|
485
|
+
c = field.coeffs
|
|
486
|
+
|
|
487
|
+
c[0:n] = self._histopolator.solve( values )
|
|
488
|
+
c.update_ghost_regions()
|
|
489
|
+
|
|
490
|
+
# ...
|
|
491
|
+
def refine(self, ncells):
|
|
492
|
+
"""
|
|
493
|
+
Create a refined 1D spline space with the given number of cells.
|
|
494
|
+
|
|
495
|
+
Parameters
|
|
496
|
+
----------
|
|
497
|
+
ncells : int
|
|
498
|
+
Number of cells of refined space. Must be multiple of self.ncells.
|
|
499
|
+
|
|
500
|
+
Returns
|
|
501
|
+
-------
|
|
502
|
+
SplineSpace
|
|
503
|
+
Refined 1D spline space which contains the original space.
|
|
504
|
+
|
|
505
|
+
"""
|
|
506
|
+
|
|
507
|
+
# Sanity checks
|
|
508
|
+
if int(ncells) != ncells:
|
|
509
|
+
msg = f"{ncells} is not an integer"
|
|
510
|
+
elif ncells < self.ncells:
|
|
511
|
+
msg = f"{ncells} is smaller than minimum value {self.ncells}"
|
|
512
|
+
elif ncells % self.ncells != 0:
|
|
513
|
+
msg = f"{ncells} is not multiple of {self.ncells}"
|
|
514
|
+
else:
|
|
515
|
+
msg = None
|
|
516
|
+
|
|
517
|
+
if msg:
|
|
518
|
+
raise ValueError("Wrong number of cells: " + msg)
|
|
519
|
+
|
|
520
|
+
if ncells == self.ncells:
|
|
521
|
+
return self
|
|
522
|
+
|
|
523
|
+
refinement_factor = ncells // self.ncells
|
|
524
|
+
grid = refine_array_1d(self.breaks, refinement_factor)
|
|
525
|
+
|
|
526
|
+
return SplineSpace(self.degree,
|
|
527
|
+
grid=grid,
|
|
528
|
+
multiplicity=self.multiplicity,
|
|
529
|
+
parent_multiplicity=self.parent_multiplicity,
|
|
530
|
+
periodic=self.periodic,
|
|
531
|
+
dirichlet=self.dirichlet,
|
|
532
|
+
basis=self.basis,
|
|
533
|
+
pads=self.pads)
|
|
534
|
+
|
|
535
|
+
# ...
|
|
536
|
+
def __str__(self):
|
|
537
|
+
"""Pretty printing"""
|
|
538
|
+
txt = '\n'
|
|
539
|
+
txt += '> ldim :: {ldim}\n'.format( ldim=self.ldim )
|
|
540
|
+
txt += '> nbasis :: {dim} \n'.format( dim=self.nbasis )
|
|
541
|
+
txt += '> degree :: {degree}'.format( degree=self.degree )
|
|
542
|
+
return txt
|
|
543
|
+
|
|
544
|
+
def draw(self):
|
|
545
|
+
from scipy.interpolate import BSpline
|
|
546
|
+
import matplotlib.pyplot as plt
|
|
547
|
+
d = self.degree
|
|
548
|
+
n = self.nbasis + d*self.periodic
|
|
549
|
+
knots = self.knots
|
|
550
|
+
fig, ax = plt.subplots()
|
|
551
|
+
xx = np.linspace(knots[0], knots[-1], 200)
|
|
552
|
+
for i in range(n):
|
|
553
|
+
c = [0]*n
|
|
554
|
+
c[i] = 1
|
|
555
|
+
spl = BSpline(knots, c, d)
|
|
556
|
+
ax.plot(xx, spl(xx), label='N{}'.format(i))
|
|
557
|
+
ax.grid(True)
|
|
558
|
+
ax.legend()
|
|
559
|
+
plt.show()
|