feectools 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. feectools/__init__.py +0 -0
  2. feectools/accelerate/__init__.py +0 -0
  3. feectools/accelerate/accelerate.py +220 -0
  4. feectools/accelerate/compile_psydac.mk +52 -0
  5. feectools/api/__init__.py +0 -0
  6. feectools/api/essential_bc.py +122 -0
  7. feectools/api/fem_bilinear_form.py +2226 -0
  8. feectools/api/fem_common.py +286 -0
  9. feectools/api/fem_sum_form.py +123 -0
  10. feectools/api/settings.py +82 -0
  11. feectools/core/__init__.py +11 -0
  12. feectools/core/bsplines.py +1107 -0
  13. feectools/core/bsplines_kernels.py +1349 -0
  14. feectools/core/field_evaluation_kernels.py +5015 -0
  15. feectools/core/tests/__init__.py +0 -0
  16. feectools/core/tests/test_bsplines.py +263 -0
  17. feectools/core/tests/test_bsplines_kernel.py +40 -0
  18. feectools/core/tests/test_bsplines_pyccel.py +752 -0
  19. feectools/ddm/__init__.py +3 -0
  20. feectools/ddm/basic.py +78 -0
  21. feectools/ddm/blocking_data_exchanger.py +348 -0
  22. feectools/ddm/cart.py +1835 -0
  23. feectools/ddm/interface_data_exchanger.py +122 -0
  24. feectools/ddm/mpi.py +109 -0
  25. feectools/ddm/nonblocking_data_exchanger.py +331 -0
  26. feectools/ddm/partition.py +207 -0
  27. feectools/ddm/petsc.py +112 -0
  28. feectools/ddm/tests/__init__.py +0 -0
  29. feectools/ddm/tests/test_cart_1d.py +138 -0
  30. feectools/ddm/tests/test_cart_2d.py +164 -0
  31. feectools/ddm/tests/test_cart_3d.py +158 -0
  32. feectools/ddm/tests/test_multicart_2d.py +173 -0
  33. feectools/ddm/tests/test_partition.py +124 -0
  34. feectools/ddm/utilities.py +24 -0
  35. feectools/feec/__init__.py +0 -0
  36. feectools/feec/derivatives.py +780 -0
  37. feectools/feec/dof_kernels.py +210 -0
  38. feectools/feec/global_geometric_projectors.py +1073 -0
  39. feectools/feec/hodge.py +148 -0
  40. feectools/fem/__init__.py +0 -0
  41. feectools/fem/basic.py +465 -0
  42. feectools/fem/grid.py +181 -0
  43. feectools/fem/partitioning.py +344 -0
  44. feectools/fem/projectors.py +160 -0
  45. feectools/fem/splines.py +559 -0
  46. feectools/fem/tensor.py +1393 -0
  47. feectools/fem/tests/__init__.py +0 -0
  48. feectools/fem/tests/analytical_profiles_1d.py +100 -0
  49. feectools/fem/tests/analytical_profiles_base.py +34 -0
  50. feectools/fem/tests/splines_error_bounds.py +155 -0
  51. feectools/fem/tests/test_spline_histopolation.py +120 -0
  52. feectools/fem/tests/test_spline_interpolation.py +182 -0
  53. feectools/fem/tests/test_splines.py +184 -0
  54. feectools/fem/tests/test_splines_par.py +46 -0
  55. feectools/fem/tests/test_vector_spaces.py +150 -0
  56. feectools/fem/tests/utilities.py +47 -0
  57. feectools/fem/vector.py +729 -0
  58. feectools/linalg/__init__.py +0 -0
  59. feectools/linalg/basic.py +1386 -0
  60. feectools/linalg/block.py +1451 -0
  61. feectools/linalg/direct_solvers.py +201 -0
  62. feectools/linalg/fft.py +258 -0
  63. feectools/linalg/kernels/__init__.py +0 -0
  64. feectools/linalg/kernels/axpy_kernels.py +57 -0
  65. feectools/linalg/kernels/inner_kernels.py +100 -0
  66. feectools/linalg/kernels/matvec_kernels.py +206 -0
  67. feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
  68. feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
  69. feectools/linalg/kernels/transpose_kernels.py +263 -0
  70. feectools/linalg/kron.py +911 -0
  71. feectools/linalg/solvers.py +1914 -0
  72. feectools/linalg/sparse.py +114 -0
  73. feectools/linalg/stencil.py +2923 -0
  74. feectools/linalg/stencil_dot_kernels.py +317 -0
  75. feectools/linalg/stencil_transpose_kernels.py +372 -0
  76. feectools/linalg/tests/__init__.py +0 -0
  77. feectools/linalg/tests/test_block.py +1588 -0
  78. feectools/linalg/tests/test_fft.py +106 -0
  79. feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
  80. feectools/linalg/tests/test_linalg.py +1065 -0
  81. feectools/linalg/tests/test_matrix_free.py +128 -0
  82. feectools/linalg/tests/test_solvers.py +213 -0
  83. feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
  84. feectools/linalg/tests/test_stencil_vector.py +1036 -0
  85. feectools/linalg/tests/test_stencil_vector_space.py +440 -0
  86. feectools/linalg/topetsc.py +522 -0
  87. feectools/linalg/utilities.py +200 -0
  88. feectools/utilities/__init__.py +0 -0
  89. feectools/utilities/quadratures.py +113 -0
  90. feectools/utilities/utils.py +166 -0
  91. feectools/version.py +1 -0
  92. feectools-0.1.0.dist-info/METADATA +66 -0
  93. feectools-0.1.0.dist-info/RECORD +98 -0
  94. feectools-0.1.0.dist-info/WHEEL +5 -0
  95. feectools-0.1.0.dist-info/entry_points.txt +3 -0
  96. feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
  97. feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
  98. feectools-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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()