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
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# Copyright 2018 Yaman Güçlü
|
|
3
|
+
|
|
4
|
+
import math
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from feectools.fem.tests.analytical_profiles_base import AnalyticalProfile
|
|
8
|
+
from feectools.fem.tests.utilities import horner, falling_factorial
|
|
9
|
+
|
|
10
|
+
__all__ = ('AnalyticalProfile_1d_cos', 'AnalyticalProfile1D_Poly')
|
|
11
|
+
|
|
12
|
+
#===============================================================================
|
|
13
|
+
class AnalyticalProfile1D_Cos( AnalyticalProfile ):
|
|
14
|
+
|
|
15
|
+
def __init__( self, n=1, c=0.0 ):
|
|
16
|
+
twopi = 2.0*math.pi
|
|
17
|
+
self._k = twopi * n
|
|
18
|
+
self._phi = twopi * c
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def ndims( self ):
|
|
22
|
+
return 1
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def domain( self ):
|
|
26
|
+
return (0.0, 1.0)
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def poly_order( self ):
|
|
30
|
+
return -1
|
|
31
|
+
|
|
32
|
+
def eval( self, x, diff=0 ):
|
|
33
|
+
return self._k**diff * np.cos( 0.5*math.pi*diff + self._k*x + self._phi )
|
|
34
|
+
|
|
35
|
+
def max_norm( self, diff=0 ):
|
|
36
|
+
return self._k**diff
|
|
37
|
+
|
|
38
|
+
#===============================================================================
|
|
39
|
+
class AnalyticalProfile1D_Sin( AnalyticalProfile ):
|
|
40
|
+
|
|
41
|
+
def __init__( self, n=1, c=0.0 ):
|
|
42
|
+
twopi = 2.0*math.pi
|
|
43
|
+
self._k = twopi * n
|
|
44
|
+
self._phi = twopi * c
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def ndims( self ):
|
|
48
|
+
return 1
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def domain( self ):
|
|
52
|
+
return (0.0, 1.0)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def poly_order( self ):
|
|
56
|
+
return -1
|
|
57
|
+
|
|
58
|
+
def eval( self, x, diff=0 ):
|
|
59
|
+
return self._k**diff * np.sin( 0.5*math.pi*diff + self._k*x + self._phi )
|
|
60
|
+
|
|
61
|
+
def max_norm( self, diff=0 ):
|
|
62
|
+
return self._k**diff
|
|
63
|
+
#===============================================================================
|
|
64
|
+
class AnalyticalProfile1D_Poly( AnalyticalProfile ):
|
|
65
|
+
|
|
66
|
+
def __init__( self, deg ):
|
|
67
|
+
|
|
68
|
+
coeffs = np.random.random_sample( 1+deg ) # 0 <= c < 1
|
|
69
|
+
coeffs = 1.0 - coeffs # 0 < c <= 1
|
|
70
|
+
|
|
71
|
+
self._deg = deg
|
|
72
|
+
self._coeffs = coeffs
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def ndims( self ):
|
|
76
|
+
return 1
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def domain( self ):
|
|
80
|
+
return (-1.0, 1.0)
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def poly_order( self ):
|
|
84
|
+
return self_deg
|
|
85
|
+
|
|
86
|
+
def eval( self, x, diff=0 ):
|
|
87
|
+
d = diff
|
|
88
|
+
coeffs = [c * falling_factorial( i+d, d ) \
|
|
89
|
+
for i,c in enumerate( self._coeffs[d:] )]
|
|
90
|
+
return horner( x, *coeffs )
|
|
91
|
+
|
|
92
|
+
def max_norm( self, diff=0 ):
|
|
93
|
+
xmin, xmax = self.domain
|
|
94
|
+
|
|
95
|
+
if xmax < abs(xmin):
|
|
96
|
+
raise NotImplementedError( "General formula not implemented" )
|
|
97
|
+
|
|
98
|
+
# For xmax >= |xmin|:
|
|
99
|
+
# max(|f^(d)(x)|) = f^(d)(xmax)
|
|
100
|
+
return self.eval( xmax, diff )
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# Copyright 2018 Yaman Güçlü
|
|
3
|
+
|
|
4
|
+
from abc import ABCMeta, abstractmethod
|
|
5
|
+
|
|
6
|
+
__all__ = ('AnalyticalProfile',)
|
|
7
|
+
|
|
8
|
+
#===============================================================================
|
|
9
|
+
class AnalyticalProfile( metaclass=ABCMeta ):
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def ndims(self):
|
|
14
|
+
""" Number of dimensions. """
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def domain(self):
|
|
19
|
+
""" Domain limits in each dimension. """
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def poly_order(self):
|
|
24
|
+
""" If profile is polynomial, poly_order=degree;
|
|
25
|
+
otherwise poly_order=-1.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def eval(self, x, diff=0):
|
|
30
|
+
""" Evaluate profile (or its derivative) at position x. """
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def max_norm(self, diff=0):
|
|
34
|
+
""" Compute max-norm of profile (or its derivative) over domain. """
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# Copyright 2018 Yaman Güçlü
|
|
3
|
+
#
|
|
4
|
+
# This file is the Python translation of a Selalib Fortran module:
|
|
5
|
+
# 'selalib/src/splines/tests/m_splines_error_bounds.F90'
|
|
6
|
+
|
|
7
|
+
__all__ = ('spline_1d_error_bound', 'spline_1d_error_bound_on_deriv',
|
|
8
|
+
'spline_2d_error_bound', 'spline_2d_error_bounds_on_grad')
|
|
9
|
+
|
|
10
|
+
#===============================================================================
|
|
11
|
+
|
|
12
|
+
k = ( 1 / 2.0,
|
|
13
|
+
1 / 8.0,
|
|
14
|
+
1 / 24.0,
|
|
15
|
+
5 / 384.0,
|
|
16
|
+
1 / 240.0,
|
|
17
|
+
61 / 46080.0,
|
|
18
|
+
17 / 40320.0,
|
|
19
|
+
277 / 2064384.0,
|
|
20
|
+
31 / 725760.0,
|
|
21
|
+
50521 / 3715891200.0 )
|
|
22
|
+
|
|
23
|
+
def tihomirov_error_bound( h, deg, norm_f ):
|
|
24
|
+
"""
|
|
25
|
+
Error bound in max norm for spline interpolation of periodic functions from:
|
|
26
|
+
|
|
27
|
+
V M Tihomirov 1969 Math. USSR Sb. 9 275
|
|
28
|
+
https://doi.org/10.1070/SM1969v009n02ABEH002052 (page 286, bottom)
|
|
29
|
+
|
|
30
|
+
Yu. S. Volkov and Yu. N. Subbotin
|
|
31
|
+
https://doi.org/10.1134/S0081543815020236 (equation 14)
|
|
32
|
+
|
|
33
|
+
Also applicable to first derivative by passing deg-1 instead of deg
|
|
34
|
+
Volkov & Subbotin 2015, eq. 15
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
h : float
|
|
39
|
+
Cell width
|
|
40
|
+
|
|
41
|
+
deg : int
|
|
42
|
+
Degree of spline S
|
|
43
|
+
|
|
44
|
+
norm_f : float
|
|
45
|
+
Max of function f(x) (or its derivative) over domain
|
|
46
|
+
|
|
47
|
+
Result
|
|
48
|
+
------
|
|
49
|
+
norm_e : float
|
|
50
|
+
Max of error $E(x):=f(x)-S(x)$ over domain
|
|
51
|
+
|
|
52
|
+
"""
|
|
53
|
+
norm_e = k[deg] * h**deg * norm_f
|
|
54
|
+
|
|
55
|
+
return norm_e
|
|
56
|
+
|
|
57
|
+
#===============================================================================
|
|
58
|
+
def spline_1d_error_bound( profile_1d, dx, deg ):
|
|
59
|
+
"""
|
|
60
|
+
Compute error bound for spline approximation of 1D analytical profile.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
profile_1d : 1D analytical profile
|
|
65
|
+
Must provide 'max_norm( n )' method to compute max norm of n-th
|
|
66
|
+
derivative of profile over domain.
|
|
67
|
+
|
|
68
|
+
dx : float
|
|
69
|
+
Grid spacing.
|
|
70
|
+
|
|
71
|
+
deg : int
|
|
72
|
+
Spline degree.
|
|
73
|
+
|
|
74
|
+
Result
|
|
75
|
+
------
|
|
76
|
+
max_error : float
|
|
77
|
+
Error bound: max-norm of error over domain should be smaller than this.
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
max_norm = profile_1d.max_norm( deg+1 )
|
|
81
|
+
max_error = tihomirov_error_bound( dx, deg, max_norm )
|
|
82
|
+
return max_error
|
|
83
|
+
|
|
84
|
+
#===============================================================================
|
|
85
|
+
def spline_1d_error_bound_on_deriv( profile_1d, dx, deg ):
|
|
86
|
+
""" Compute error bound on first derivative, for spline approximation of 1D
|
|
87
|
+
analytical profile. Signature is identical to 'spline_1d_error_bound'.
|
|
88
|
+
"""
|
|
89
|
+
max_norm = profile_1d.max_norm( deg+1 )
|
|
90
|
+
max_error = tihomirov_error_bound( dx, deg-1, max_norm )
|
|
91
|
+
return max_error
|
|
92
|
+
|
|
93
|
+
#===============================================================================
|
|
94
|
+
def spline_2d_error_bound( profile_2d, dx1, dx2, deg1, deg2 ):
|
|
95
|
+
"""
|
|
96
|
+
Compute error bound for spline approximation of 2D analytical profile.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
profile_2d : 2D analytical profile
|
|
101
|
+
Must provide 'max_norm( n1,n2 )' method to compute max norm of its
|
|
102
|
+
mixed derivative of degree (n1,n2) over domain.
|
|
103
|
+
|
|
104
|
+
dx1 : float
|
|
105
|
+
Grid spacing along 1st dimension.
|
|
106
|
+
|
|
107
|
+
dx2 : float
|
|
108
|
+
Grid spacing along 2nd dimension.
|
|
109
|
+
|
|
110
|
+
deg1 : int
|
|
111
|
+
Spline degree along 1st dimension.
|
|
112
|
+
|
|
113
|
+
deg2 : int
|
|
114
|
+
Spline degree along 2nd dimension.
|
|
115
|
+
|
|
116
|
+
Result
|
|
117
|
+
------
|
|
118
|
+
max_error : float
|
|
119
|
+
Error bound: max-norm of error over domain should be smaller than this.
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
# Max norm of highest partial derivatives in x1 and x2 of analytical profile
|
|
123
|
+
max_norm1 = profile_2d.max_norm( deg1+1, 0 )
|
|
124
|
+
max_norm2 = profile_2d.max_norm( 0 , deg2+1 )
|
|
125
|
+
|
|
126
|
+
# Error bound on function value
|
|
127
|
+
max_error = f_tihomirov_error_bound( dx1, deg1, max_norm1 ) \
|
|
128
|
+
+ f_tihomirov_error_bound( dx2, deg2, max_norm2 )
|
|
129
|
+
|
|
130
|
+
# Empirical correction: for linear interpolation increase estimate by 5%
|
|
131
|
+
if (deg1 == 1 or deg2 == 1):
|
|
132
|
+
max_error = 1.05 * max_error
|
|
133
|
+
|
|
134
|
+
return max_error
|
|
135
|
+
|
|
136
|
+
#===============================================================================
|
|
137
|
+
def spline_2d_error_bounds_on_grad( profile_2d, dx1, dx2, deg1, deg2 ):
|
|
138
|
+
"""
|
|
139
|
+
Compute error bound on gradient, for spline approximation of 2D
|
|
140
|
+
analytical profile. Signature is identical to 'spline_2d_error_bound'.
|
|
141
|
+
|
|
142
|
+
"""
|
|
143
|
+
# Max norm of highest partial derivatives in x1 and x2 of analytical profile
|
|
144
|
+
max_norm1 = profile_2d.max_norm( deg1+1, 0 )
|
|
145
|
+
max_norm2 = profile_2d.max_norm( 0 , deg2+1 )
|
|
146
|
+
|
|
147
|
+
# Error bound on x1-derivative
|
|
148
|
+
max_error1 = f_tihomirov_error_bound( dx1, deg1-1, max_norm1 ) \
|
|
149
|
+
+ f_tihomirov_error_bound( dx2, deg2 , max_norm2 )
|
|
150
|
+
|
|
151
|
+
# Error bound on x2-derivative
|
|
152
|
+
max_error2 = f_tihomirov_error_bound( dx1, deg1 , max_norm1 ) \
|
|
153
|
+
+ f_tihomirov_error_bound( dx2, deg2-1, max_norm2 )
|
|
154
|
+
|
|
155
|
+
return (max_error1, max_error2)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import numpy as np
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
from scipy.integrate import quad
|
|
5
|
+
|
|
6
|
+
from feectools.fem.basic import FemField
|
|
7
|
+
from feectools.fem.splines import SplineSpace
|
|
8
|
+
#from feectools.fem.tensor import TensorFemSpace
|
|
9
|
+
|
|
10
|
+
from feectools.fem.tests.utilities import horner, random_grid
|
|
11
|
+
from feectools.fem.tests.splines_error_bounds import spline_1d_error_bound
|
|
12
|
+
from feectools.fem.tests.analytical_profiles_1d import AnalyticalProfile1D_Cos
|
|
13
|
+
|
|
14
|
+
#==============================================================================
|
|
15
|
+
def histopolate_polynomial(basis, ncells, degree):
|
|
16
|
+
|
|
17
|
+
domain = (-1.0, 1.0)
|
|
18
|
+
periodic = False
|
|
19
|
+
|
|
20
|
+
# Polynomial to be approximated
|
|
21
|
+
poly_coeffs = np.random.random_sample( degree+1 ) # 0 <= c < 1
|
|
22
|
+
poly_coeffs = 1.0 - poly_coeffs # 0 < c <= 1
|
|
23
|
+
f = lambda x : horner( x, *poly_coeffs )
|
|
24
|
+
|
|
25
|
+
# Define spline space and field
|
|
26
|
+
grid = random_grid( domain, ncells, 0.5 )
|
|
27
|
+
Vh = SplineSpace( degree=degree, grid=grid, periodic=periodic, basis=basis )
|
|
28
|
+
fh = FemField( Vh )
|
|
29
|
+
|
|
30
|
+
# Compute histopolant
|
|
31
|
+
xg = Vh.ext_greville
|
|
32
|
+
Ig = np.array([quad(f, xg[i], xg[i+1])[0] for i in range(len(xg)-1)])
|
|
33
|
+
Vh.compute_histopolant(Ig, fh)
|
|
34
|
+
|
|
35
|
+
return domain, f, fh
|
|
36
|
+
|
|
37
|
+
#==============================================================================
|
|
38
|
+
@pytest.mark.parametrize('basis', ['B', 'M'])
|
|
39
|
+
@pytest.mark.parametrize('ncells', [10, 20, 33])
|
|
40
|
+
@pytest.mark.parametrize('degree', [2, 5, 7])
|
|
41
|
+
def test_histopolation_exact(basis, ncells, degree, num_pts=100, tol=1e-11):
|
|
42
|
+
|
|
43
|
+
domain, f, fh = histopolate_polynomial(basis, ncells, degree)
|
|
44
|
+
|
|
45
|
+
# Compare to exact solution
|
|
46
|
+
x = np.linspace(*domain, num=num_pts)
|
|
47
|
+
y = f(x)
|
|
48
|
+
yh = np.array([fh(xi) for xi in x])
|
|
49
|
+
|
|
50
|
+
assert np.allclose(yh, y, rtol=tol, atol=tol)
|
|
51
|
+
|
|
52
|
+
#==============================================================================
|
|
53
|
+
@pytest.mark.parametrize('basis', ['B', 'M'])
|
|
54
|
+
@pytest.mark.parametrize('ncells', [10, 20, 33])
|
|
55
|
+
@pytest.mark.parametrize('degree', [2, 5, 7])
|
|
56
|
+
@pytest.mark.parametrize('periodic', [True, False])
|
|
57
|
+
def test_histopolation_cosine(basis, ncells, degree, periodic, num_pts=100):
|
|
58
|
+
|
|
59
|
+
# Function to be approximated
|
|
60
|
+
# TODO: write function and domain explicitly
|
|
61
|
+
f = AnalyticalProfile1D_Cos()
|
|
62
|
+
|
|
63
|
+
# Define spline space and field
|
|
64
|
+
grid, dx = np.linspace(*f.domain, num=ncells+1, retstep=True)
|
|
65
|
+
Vh = SplineSpace(degree=degree, grid=grid, periodic=periodic)
|
|
66
|
+
fh = FemField(Vh)
|
|
67
|
+
|
|
68
|
+
# Compute histopolant
|
|
69
|
+
xg = Vh.histopolation_grid
|
|
70
|
+
Ig = np.array([quad(f.eval, xl, xr)[0] for xl, xr in zip(xg[:-1], xg[1:])])
|
|
71
|
+
Vh.compute_histopolant(Ig, fh)
|
|
72
|
+
|
|
73
|
+
# Compare to exact solution
|
|
74
|
+
x = np.linspace(*f.domain, num=num_pts)
|
|
75
|
+
y = f.eval(x)
|
|
76
|
+
yh = np.array([fh(xi) for xi in x])
|
|
77
|
+
|
|
78
|
+
max_norm_err = np.max(abs(y - yh))
|
|
79
|
+
err_bound = spline_1d_error_bound(f, dx, degree)
|
|
80
|
+
|
|
81
|
+
assert max_norm_err < err_bound
|
|
82
|
+
|
|
83
|
+
#==============================================================================
|
|
84
|
+
# Diagnostics
|
|
85
|
+
#==============================================================================
|
|
86
|
+
def compare_and_plot(domain, f, fh, num_pts=100):
|
|
87
|
+
|
|
88
|
+
x = np.linspace(*domain, num=num_pts)
|
|
89
|
+
y = f(x)
|
|
90
|
+
yh = np.array([fh(xi) for xi in x])
|
|
91
|
+
|
|
92
|
+
max_norm_err = np.max(abs(yh - y))
|
|
93
|
+
print("Maximum error on evaluation grid: {}".format(max_norm_err))
|
|
94
|
+
|
|
95
|
+
fig, ax = plt.subplots(1, 1)
|
|
96
|
+
ax.plot(x, y, label='f(x)')
|
|
97
|
+
ax.plot(x, yh, '.', label='f1_h(x)')
|
|
98
|
+
ax.grid(True)
|
|
99
|
+
ax.set_xlabel('x')
|
|
100
|
+
ax.set_ylabel('y')
|
|
101
|
+
fig.legend()
|
|
102
|
+
fig.show()
|
|
103
|
+
|
|
104
|
+
#==============================================================================
|
|
105
|
+
# Diagnostics
|
|
106
|
+
#==============================================================================
|
|
107
|
+
if __name__ == '__main__':
|
|
108
|
+
|
|
109
|
+
domain, f, fh = histopolate_polynomial(basis='B', ncells=10, degree=7)
|
|
110
|
+
compare_and_plot(domain, f, fh)
|
|
111
|
+
|
|
112
|
+
domain, f, fh = histopolate_polynomial(basis='M', ncells=10, degree=7)
|
|
113
|
+
compare_and_plot(domain, f, fh)
|
|
114
|
+
|
|
115
|
+
import __main__ as main
|
|
116
|
+
if hasattr( main, '__file__' ):
|
|
117
|
+
try:
|
|
118
|
+
__IPYTHON__
|
|
119
|
+
except NameError:
|
|
120
|
+
plt.show()
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# Copyright 2018 Yaman Güçlü
|
|
3
|
+
|
|
4
|
+
from feectools.ddm.mpi import mpi as MPI
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pytest
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from feectools.core.bsplines import make_knots
|
|
11
|
+
from feectools.fem.basic import FemField
|
|
12
|
+
from feectools.fem.splines import SplineSpace
|
|
13
|
+
from feectools.fem.tensor import TensorFemSpace
|
|
14
|
+
from feectools.ddm.cart import DomainDecomposition
|
|
15
|
+
|
|
16
|
+
from feectools.fem.tests.utilities import horner, random_grid
|
|
17
|
+
from feectools.fem.tests.splines_error_bounds import spline_1d_error_bound
|
|
18
|
+
from feectools.fem.tests.analytical_profiles_1d import (AnalyticalProfile1D_Cos, AnalyticalProfile1D_Poly)
|
|
19
|
+
#===============================================================================
|
|
20
|
+
@pytest.mark.serial
|
|
21
|
+
@pytest.mark.parametrize( "ncells", [1,5,10,23] )
|
|
22
|
+
@pytest.mark.parametrize( "degree", range(1,11) )
|
|
23
|
+
|
|
24
|
+
def test_SplineInterpolation1D_exact( ncells, degree ):
|
|
25
|
+
|
|
26
|
+
domain = [-1.0, 1.0]
|
|
27
|
+
periodic = False
|
|
28
|
+
|
|
29
|
+
poly_coeffs = np.random.random_sample( degree+1 ) # 0 <= c < 1
|
|
30
|
+
poly_coeffs = 1.0 - poly_coeffs # 0 < c <= 1
|
|
31
|
+
f = lambda x : horner( x, *poly_coeffs )
|
|
32
|
+
|
|
33
|
+
grid = random_grid( domain, ncells, 0.5 )
|
|
34
|
+
space = SplineSpace( degree=degree, grid=grid, periodic=periodic )
|
|
35
|
+
field = FemField( space )
|
|
36
|
+
|
|
37
|
+
xg = space.greville
|
|
38
|
+
ug = f( xg )
|
|
39
|
+
|
|
40
|
+
space.compute_interpolant( ug, field )
|
|
41
|
+
|
|
42
|
+
xt = np.linspace( *domain, num=100 )
|
|
43
|
+
err = np.array( [field( x ) - f( x ) for x in xt] )
|
|
44
|
+
|
|
45
|
+
max_norm_err = np.max( abs( err ) )
|
|
46
|
+
assert max_norm_err < 1.0e-13
|
|
47
|
+
|
|
48
|
+
#===============================================================================
|
|
49
|
+
def args_SplineInterpolation1D_cosine():
|
|
50
|
+
for ncells in [5,10,23]:
|
|
51
|
+
for periodic in [True, False]:
|
|
52
|
+
pmax = min(ncells,9) if periodic else 9
|
|
53
|
+
for degree in range(1,pmax+1):
|
|
54
|
+
yield (ncells, degree, periodic)
|
|
55
|
+
|
|
56
|
+
@pytest.mark.serial
|
|
57
|
+
@pytest.mark.parametrize( "ncells,degree,periodic",
|
|
58
|
+
args_SplineInterpolation1D_cosine() )
|
|
59
|
+
|
|
60
|
+
def test_SplineInterpolation1D_cosine( ncells, degree, periodic ):
|
|
61
|
+
|
|
62
|
+
f = AnalyticalProfile1D_Cos()
|
|
63
|
+
|
|
64
|
+
grid, dx = np.linspace( *f.domain, num=ncells+1, retstep=True )
|
|
65
|
+
space = SplineSpace( degree=degree, grid=grid, periodic=periodic )
|
|
66
|
+
field = FemField( space )
|
|
67
|
+
|
|
68
|
+
xg = space.greville
|
|
69
|
+
ug = f.eval( xg )
|
|
70
|
+
|
|
71
|
+
space.compute_interpolant( ug, field )
|
|
72
|
+
xt = np.linspace( *f.domain, num=100 )
|
|
73
|
+
err = np.array( [field( x ) - f.eval( x ) for x in xt] )
|
|
74
|
+
|
|
75
|
+
max_norm_err = np.max( abs( err ) )
|
|
76
|
+
err_bound = spline_1d_error_bound( f, dx, degree )
|
|
77
|
+
|
|
78
|
+
assert max_norm_err < err_bound
|
|
79
|
+
|
|
80
|
+
#===============================================================================
|
|
81
|
+
@pytest.mark.parallel
|
|
82
|
+
@pytest.mark.parametrize( "nc1", [7,10,23] )
|
|
83
|
+
@pytest.mark.parametrize( "nc2", [7,10,23] )
|
|
84
|
+
@pytest.mark.parametrize( "deg1", range(1,5) )
|
|
85
|
+
@pytest.mark.parametrize( "deg2", range(1,5) )
|
|
86
|
+
|
|
87
|
+
def test_SplineInterpolation2D_parallel_exact( nc1, nc2, deg1, deg2 ):
|
|
88
|
+
|
|
89
|
+
# Communicator, size, rank
|
|
90
|
+
mpi_comm = MPI.COMM_WORLD
|
|
91
|
+
mpi_size = mpi_comm.Get_size()
|
|
92
|
+
mpi_rank = mpi_comm.Get_rank()
|
|
93
|
+
|
|
94
|
+
domain1 = [-1.0, 0.8]
|
|
95
|
+
periodic1 = False
|
|
96
|
+
|
|
97
|
+
domain2 = [-0.9, 1.0]
|
|
98
|
+
periodic2 = False
|
|
99
|
+
|
|
100
|
+
# Random coefficients of 1D polynomial (identical on all processes!)
|
|
101
|
+
poly_coeffs = np.random.random_sample( min(deg1,deg2)+1 ) # 0 <= c < 1
|
|
102
|
+
poly_coeffs = 1.0 - poly_coeffs # 0 < c <= 1
|
|
103
|
+
mpi_comm.Bcast( poly_coeffs, root=0 )
|
|
104
|
+
|
|
105
|
+
# 2D exact solution: 1D polynomial of linear combination z=x1-x2/2
|
|
106
|
+
f = lambda x1,x2 : horner( x1-0.5*x2, *poly_coeffs )
|
|
107
|
+
|
|
108
|
+
# Random 1D grids (identical on all processes!)
|
|
109
|
+
grid1 = random_grid( domain1, nc1, 0.1 )
|
|
110
|
+
grid2 = random_grid( domain2, nc2, 0.1 )
|
|
111
|
+
mpi_comm.Bcast( grid1, root=0 )
|
|
112
|
+
mpi_comm.Bcast( grid2, root=0 )
|
|
113
|
+
|
|
114
|
+
# 1D spline spaces along x1 and x2
|
|
115
|
+
space1 = SplineSpace( degree=deg1, grid=grid1, periodic=periodic1 )
|
|
116
|
+
space2 = SplineSpace( degree=deg2, grid=grid2, periodic=periodic2 )
|
|
117
|
+
|
|
118
|
+
domain_decomposition = DomainDecomposition([nc1, nc2], [periodic1, periodic2], comm=mpi_comm)
|
|
119
|
+
# Tensor-product 2D spline space, distributed, and field
|
|
120
|
+
tensor_space = TensorFemSpace( domain_decomposition, space1, space2 )
|
|
121
|
+
tensor_field = FemField( tensor_space )
|
|
122
|
+
|
|
123
|
+
# Coordinates of Greville points (global)
|
|
124
|
+
x1g = space1.greville
|
|
125
|
+
x2g = space2.greville
|
|
126
|
+
|
|
127
|
+
# Interpolation data on Greville points (distributed)
|
|
128
|
+
V = tensor_space.coeff_space
|
|
129
|
+
ug = V.zeros()
|
|
130
|
+
s1,s2 = V.starts
|
|
131
|
+
e1,e2 = V.ends
|
|
132
|
+
n1,n2 = V.npts
|
|
133
|
+
for i1 in range( s1, e1+1 ):
|
|
134
|
+
for i2 in range( s2, e2+1 ):
|
|
135
|
+
ug[i1,i2] = f( x1g[i1], x2g[i2] )
|
|
136
|
+
|
|
137
|
+
ug.update_ghost_regions()
|
|
138
|
+
|
|
139
|
+
# Compute 2D spline interpolant
|
|
140
|
+
tensor_space.compute_interpolant( ug, tensor_field )
|
|
141
|
+
|
|
142
|
+
#------------
|
|
143
|
+
# DIAGNOSTICS
|
|
144
|
+
#------------
|
|
145
|
+
|
|
146
|
+
# Verify that solution is exact at Greville points
|
|
147
|
+
err = V.zeros()
|
|
148
|
+
for i1 in range( s1, e1+1 ):
|
|
149
|
+
for i2 in range( s2, e2+1 ):
|
|
150
|
+
err[i1,i2] = ug[i1,i2] - tensor_field( x1g[i1], x2g[i2] )
|
|
151
|
+
interp_error = abs( err[:,:] ).max()
|
|
152
|
+
|
|
153
|
+
# Compute L2 norm of error
|
|
154
|
+
integrand = lambda x1,x2: (f(x1,x2)-tensor_field(x1,x2))**2
|
|
155
|
+
l2_error = np.sqrt( tensor_space.integral( integrand ) )
|
|
156
|
+
|
|
157
|
+
# Print some information to terminal
|
|
158
|
+
for i in range( mpi_size ):
|
|
159
|
+
if i == mpi_rank:
|
|
160
|
+
print( '--------------------------------------------------' )
|
|
161
|
+
print( ' RANK = {}'.format( mpi_rank ) )
|
|
162
|
+
print( '--------------------------------------------------' )
|
|
163
|
+
print( '> Degree :: [{:2d},{:2d}]'.format( deg1, deg2 ) )
|
|
164
|
+
print( '> Ncells :: [{:2d},{:2d}]'.format( nc1, nc2 ) )
|
|
165
|
+
print( '> Nbasis :: [{:2d},{:2d}]'.format( n1, n2 ) )
|
|
166
|
+
print( '> Starts :: [{:2d},{:2d}]'.format( s1, s2 ) )
|
|
167
|
+
print( '> Ends :: [{:2d},{:2d}]'.format( e1, e2 ) )
|
|
168
|
+
print( '> Interp. error :: {:.2e}'.format( interp_error ) )
|
|
169
|
+
print( '> L2 error :: {:.2e}'.format( l2_error ) )
|
|
170
|
+
print( '', flush=True )
|
|
171
|
+
time.sleep( 0.1 )
|
|
172
|
+
mpi_comm.Barrier()
|
|
173
|
+
|
|
174
|
+
# Verify that error is only caused by finite precision arithmetic
|
|
175
|
+
assert interp_error < 1.0e-13
|
|
176
|
+
assert l2_error < 1.0e-13
|
|
177
|
+
|
|
178
|
+
#===============================================================================
|
|
179
|
+
# SCRIPT FUNCTIONALITY
|
|
180
|
+
#===============================================================================
|
|
181
|
+
if __name__ == '__main__':
|
|
182
|
+
test_SplineInterpolation2D_parallel_exact( 10, 16, 3, 5 )
|