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/grid.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2018 Yaman Güçlü
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from feectools.core.bsplines import elements_spans
|
|
8
|
+
from feectools.core.bsplines import quadrature_grid
|
|
9
|
+
from feectools.core.bsplines import basis_ders_on_quad_grid
|
|
10
|
+
from feectools.core.bsplines import elevate_knots
|
|
11
|
+
from feectools.utilities.quadratures import gauss_legendre
|
|
12
|
+
from feectools.fem.splines import SplineSpace
|
|
13
|
+
|
|
14
|
+
__all__ = ('FemAssemblyGrid',)
|
|
15
|
+
|
|
16
|
+
#==============================================================================
|
|
17
|
+
class FemAssemblyGrid:
|
|
18
|
+
"""
|
|
19
|
+
Class that collects all 1D information local to process that are necessary
|
|
20
|
+
for the correct assembly of l.h.s. matrix and r.h.s. vector in a finite
|
|
21
|
+
element method.
|
|
22
|
+
|
|
23
|
+
This works in the case of clamped and periodic splines, for a global or
|
|
24
|
+
distributed domain.
|
|
25
|
+
|
|
26
|
+
A 'TensorFemSpace' object will create one object of this class for each
|
|
27
|
+
1D space.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
space : SplineSpace
|
|
32
|
+
1D finite element space.
|
|
33
|
+
|
|
34
|
+
start : int
|
|
35
|
+
Index of first element local to process.
|
|
36
|
+
|
|
37
|
+
end : int
|
|
38
|
+
Index of last element local to process.
|
|
39
|
+
|
|
40
|
+
nquads : int
|
|
41
|
+
Number of quadrature points used in the Gauss-Legendre quadrature formula.
|
|
42
|
+
|
|
43
|
+
nderiv : int
|
|
44
|
+
Number of basis functions' derivatives to be precomputed at the Gauss
|
|
45
|
+
points (default: 1).
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
def __init__(self, space, start, end, *, nquads, nderiv=1):
|
|
49
|
+
|
|
50
|
+
assert isinstance(space, SplineSpace)
|
|
51
|
+
assert isinstance(start, int)
|
|
52
|
+
assert isinstance(end, int)
|
|
53
|
+
assert isinstance(nquads, int)
|
|
54
|
+
assert isinstance(nderiv, int)
|
|
55
|
+
|
|
56
|
+
# Useful shortcuts
|
|
57
|
+
T = space.knots # knots sequence
|
|
58
|
+
degree = space.degree # spline degree
|
|
59
|
+
n = space.nbasis # total number of control points
|
|
60
|
+
grid = space.breaks # breakpoints
|
|
61
|
+
|
|
62
|
+
# Gauss-legendre quadrature rule
|
|
63
|
+
# print('[psy4str] here is a problem')
|
|
64
|
+
u, w = gauss_legendre(nquads) # TODO [psy4str] OLD DOESN'T WORK
|
|
65
|
+
# u, w = gauss_legendre(nquads + 1) # TODO [psy4str] NEW WORKS BUT WHY?
|
|
66
|
+
|
|
67
|
+
#-------------------------------------------
|
|
68
|
+
# GLOBAL GRID
|
|
69
|
+
#-------------------------------------------
|
|
70
|
+
|
|
71
|
+
# Lists of quadrature coordinates and weights on each element
|
|
72
|
+
global_points, global_weights = quadrature_grid(grid, u, w)
|
|
73
|
+
|
|
74
|
+
# List of basis function values on each element
|
|
75
|
+
global_basis = basis_ders_on_quad_grid(T, degree, global_points, nderiv, space.basis)
|
|
76
|
+
|
|
77
|
+
# List of spans on each element
|
|
78
|
+
# (Span is global index of last non-vanishing basis function)
|
|
79
|
+
global_spans = elements_spans(T, degree)
|
|
80
|
+
|
|
81
|
+
grid = grid[start : end + 2]
|
|
82
|
+
spans = global_spans [start : end + 1].copy()
|
|
83
|
+
basis = global_basis [start : end + 1].copy()
|
|
84
|
+
points = global_points [start : end + 1].copy()
|
|
85
|
+
weights = global_weights[start : end + 1].copy()
|
|
86
|
+
|
|
87
|
+
#-------------------------------------------
|
|
88
|
+
# DATA STORAGE IN OBJECT
|
|
89
|
+
#-------------------------------------------
|
|
90
|
+
|
|
91
|
+
# Quadrature data on extended distributed domain
|
|
92
|
+
self._num_elements = len(grid) - 1
|
|
93
|
+
self._num_quad_pts = len(u)
|
|
94
|
+
self._spans = spans
|
|
95
|
+
self._basis = basis
|
|
96
|
+
self._points = points
|
|
97
|
+
self._weights = weights
|
|
98
|
+
self._indices = tuple(range(start, end + 1))
|
|
99
|
+
self._quad_rule_x = u
|
|
100
|
+
self._quad_rule_w = w
|
|
101
|
+
|
|
102
|
+
# Local index of start/end elements of domain partitioning
|
|
103
|
+
self._local_element_start = 0
|
|
104
|
+
self._local_element_end = self._num_elements - 1
|
|
105
|
+
|
|
106
|
+
# ...
|
|
107
|
+
@property
|
|
108
|
+
def num_elements(self):
|
|
109
|
+
""" Number of elements over which integration should be performed.
|
|
110
|
+
"""
|
|
111
|
+
return self._num_elements
|
|
112
|
+
|
|
113
|
+
# ...
|
|
114
|
+
@property
|
|
115
|
+
def num_quad_pts(self):
|
|
116
|
+
""" Number of quadrature points in each element.
|
|
117
|
+
"""
|
|
118
|
+
return self._num_quad_pts
|
|
119
|
+
|
|
120
|
+
# ...
|
|
121
|
+
@property
|
|
122
|
+
def spans(self):
|
|
123
|
+
""" Span index in each element.
|
|
124
|
+
"""
|
|
125
|
+
return self._spans
|
|
126
|
+
|
|
127
|
+
# ...
|
|
128
|
+
@property
|
|
129
|
+
def basis(self):
|
|
130
|
+
""" Basis function values (and their derivatives) at each quadrature point.
|
|
131
|
+
"""
|
|
132
|
+
return self._basis
|
|
133
|
+
|
|
134
|
+
# ...
|
|
135
|
+
@property
|
|
136
|
+
def points(self):
|
|
137
|
+
""" Location of each quadrature point.
|
|
138
|
+
"""
|
|
139
|
+
return self._points
|
|
140
|
+
|
|
141
|
+
# ...
|
|
142
|
+
@property
|
|
143
|
+
def weights(self):
|
|
144
|
+
""" Weight assigned to each quadrature point.
|
|
145
|
+
"""
|
|
146
|
+
return self._weights
|
|
147
|
+
|
|
148
|
+
# ...
|
|
149
|
+
@property
|
|
150
|
+
def indices(self):
|
|
151
|
+
""" Global index of each element used in assembly process.
|
|
152
|
+
"""
|
|
153
|
+
return self._indices
|
|
154
|
+
|
|
155
|
+
# ...
|
|
156
|
+
@property
|
|
157
|
+
def quad_rule_x(self):
|
|
158
|
+
""" Coordinates of quadrature points on canonical interval [-1,1].
|
|
159
|
+
"""
|
|
160
|
+
return self._quad_rule_x
|
|
161
|
+
|
|
162
|
+
# ...
|
|
163
|
+
@property
|
|
164
|
+
def quad_rule_w(self):
|
|
165
|
+
""" Weights assigned to quadrature points on canonical interval [-1,1].
|
|
166
|
+
"""
|
|
167
|
+
return self._quad_rule_w
|
|
168
|
+
|
|
169
|
+
# ...
|
|
170
|
+
@property
|
|
171
|
+
def local_element_start(self):
|
|
172
|
+
""" Local index of first element owned by process.
|
|
173
|
+
"""
|
|
174
|
+
return self._local_element_start
|
|
175
|
+
|
|
176
|
+
# ...
|
|
177
|
+
@property
|
|
178
|
+
def local_element_end(self):
|
|
179
|
+
""" Local index of last element owned by process.
|
|
180
|
+
"""
|
|
181
|
+
return self._local_element_end
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from feectools.ddm.cart import CartDecomposition, InterfaceCartDecomposition, create_interfaces_cart
|
|
7
|
+
from feectools.core.bsplines import elements_spans
|
|
8
|
+
from feectools.fem.vector import VectorFemSpace
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
'partition_coefficients',
|
|
13
|
+
'get_minus_starts_ends',
|
|
14
|
+
'get_plus_starts_ends',
|
|
15
|
+
'create_cart',
|
|
16
|
+
'construct_interface_spaces',
|
|
17
|
+
'construct_reduced_interface_spaces'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def partition_coefficients(domain_decomposition, spaces, min_blocks=None):
|
|
22
|
+
"""
|
|
23
|
+
Partition the coefficients starting from the grid decomposition.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
|
|
28
|
+
domain_decomposition: DomainDecomposition
|
|
29
|
+
The distributed topological domain.
|
|
30
|
+
|
|
31
|
+
spaces: list of SplineSpace
|
|
32
|
+
The 1d spline spaces that construct the tensor fem space.
|
|
33
|
+
|
|
34
|
+
min_blocks: list of int
|
|
35
|
+
The minimum number of coefficients owned by a process.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
|
|
40
|
+
global_starts: list of list of int
|
|
41
|
+
The starts of the coefficients for every process along each direction.
|
|
42
|
+
|
|
43
|
+
global_ends: list of list of int
|
|
44
|
+
The ends of the coefficients for every process along each direction.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
npts = [V.nbasis for V in spaces]
|
|
48
|
+
multiplicity = [V.multiplicity for V in spaces]
|
|
49
|
+
|
|
50
|
+
ndims = len(npts)
|
|
51
|
+
global_starts = [None] * ndims
|
|
52
|
+
global_ends = [None] * ndims
|
|
53
|
+
|
|
54
|
+
for axis in range(ndims):
|
|
55
|
+
es = domain_decomposition.global_element_starts[axis]
|
|
56
|
+
ee = domain_decomposition.global_element_ends [axis]
|
|
57
|
+
m = multiplicity[axis]
|
|
58
|
+
|
|
59
|
+
global_ends [axis] = m*(ee+1)-1
|
|
60
|
+
global_ends [axis][-1] = npts[axis]-1
|
|
61
|
+
global_starts[axis] = np.array([0] + (global_ends[axis][:-1]+1).tolist())
|
|
62
|
+
|
|
63
|
+
if min_blocks is None:
|
|
64
|
+
min_blocks = [None] * ndims
|
|
65
|
+
|
|
66
|
+
for s, e, V, mb in zip(global_starts, global_ends, spaces, min_blocks):
|
|
67
|
+
if V.periodic or mb is None:
|
|
68
|
+
assert all(e-s+1 >= V.degree), f"Local number of elements (after domain decomposition) is to small for spline degree p={V.degree}: {e-s+1} is not >= {V.degree} everywhere.\n \
|
|
69
|
+
You can:\n \
|
|
70
|
+
1. increase Nel\n \
|
|
71
|
+
2. lower p\n \
|
|
72
|
+
3. decrease the MPI size."
|
|
73
|
+
else:
|
|
74
|
+
assert all(e-s+1 >= mb)
|
|
75
|
+
|
|
76
|
+
return global_starts, global_ends
|
|
77
|
+
|
|
78
|
+
#------------------------------------------------------------------------------
|
|
79
|
+
def get_minus_starts_ends(plus_starts, plus_ends, minus_npts, plus_npts, minus_axis, plus_axis,
|
|
80
|
+
minus_ext, plus_ext, minus_pads, plus_pads, minus_shifts, plus_shifts,
|
|
81
|
+
diff):
|
|
82
|
+
"""
|
|
83
|
+
Compute the coefficients needed by the minus patch in a given interface.
|
|
84
|
+
"""
|
|
85
|
+
starts = [max(0,s-m*p) for s,m,p in zip(plus_starts, minus_shifts, minus_pads)]
|
|
86
|
+
ends = [min(n,e+m*p) for e,n,m,p in zip(plus_ends, minus_npts, minus_shifts, minus_pads)]
|
|
87
|
+
starts[minus_axis] = 0 if minus_ext == -1 else ends[minus_axis]-minus_pads[minus_axis]
|
|
88
|
+
ends[minus_axis] = ends[minus_axis] if minus_ext == 1 else minus_pads[minus_axis]
|
|
89
|
+
return starts, ends
|
|
90
|
+
|
|
91
|
+
#------------------------------------------------------------------------------
|
|
92
|
+
def get_plus_starts_ends(minus_starts, minus_ends, minus_npts, plus_npts, minus_axis, plus_axis,
|
|
93
|
+
minus_ext, plus_ext, minus_pads, plus_pads, minus_shifts, plus_shifts,
|
|
94
|
+
diff):
|
|
95
|
+
"""
|
|
96
|
+
Compute the coefficients needed by the plus patch in a given interface.
|
|
97
|
+
"""
|
|
98
|
+
starts = [max(0,s-m*p) for s,m,p in zip(minus_starts, plus_shifts, plus_pads)]
|
|
99
|
+
ends = [min(n,e+m*p) for e,n,m,p in zip(minus_ends, plus_npts, plus_shifts, plus_pads)]
|
|
100
|
+
starts[plus_axis] = 0 if plus_ext == -1 else ends[plus_axis]-plus_pads[plus_axis]
|
|
101
|
+
ends[plus_axis] = ends[plus_axis] if plus_ext == 1 else plus_pads[plus_axis]
|
|
102
|
+
return starts, ends
|
|
103
|
+
|
|
104
|
+
#------------------------------------------------------------------------------
|
|
105
|
+
def create_cart(domain_decomposition, spaces):
|
|
106
|
+
"""
|
|
107
|
+
Compute the cartesian decomposition of the coefficient space.
|
|
108
|
+
Two different cases are possible:
|
|
109
|
+
|
|
110
|
+
- Single patch :
|
|
111
|
+
We distribute the coefficients using all the processes provided by the given communicator.
|
|
112
|
+
- Multiple patches :
|
|
113
|
+
We decompose the provided communicator in a list of smaller disjoint intra-communicators,
|
|
114
|
+
and decompose the coefficients of each patch with an assigned intra-communicator.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
domain_decomposition : DomainDecomposition | tuple of DomainDecomposition
|
|
119
|
+
|
|
120
|
+
spaces : list of list of 1D global Spline spaces
|
|
121
|
+
The 1D global spline spaces that will be distributed.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
cart : tuple of CartDecomposition
|
|
126
|
+
Cartesian decomposition of the coefficient space for each patch in the domain.
|
|
127
|
+
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
if len(spaces) == 1:
|
|
131
|
+
domain_decomposition = domain_decomposition[0]
|
|
132
|
+
spaces = spaces[0]
|
|
133
|
+
npts = [V.nbasis for V in spaces]
|
|
134
|
+
pads = [V._pads for V in spaces]
|
|
135
|
+
multiplicity = [V.multiplicity for V in spaces]
|
|
136
|
+
|
|
137
|
+
global_starts, global_ends = partition_coefficients(domain_decomposition, spaces)
|
|
138
|
+
|
|
139
|
+
carts = [CartDecomposition(
|
|
140
|
+
domain_decomposition = domain_decomposition,
|
|
141
|
+
npts = npts,
|
|
142
|
+
global_starts = global_starts,
|
|
143
|
+
global_ends = global_ends,
|
|
144
|
+
pads = pads,
|
|
145
|
+
shifts = multiplicity)]
|
|
146
|
+
else:
|
|
147
|
+
carts = []
|
|
148
|
+
for i in range(len(spaces)):
|
|
149
|
+
npts = [V.nbasis for V in spaces[i]]
|
|
150
|
+
pads = [V._pads for V in spaces[i]]
|
|
151
|
+
multiplicity = [V.multiplicity for V in spaces[i]]
|
|
152
|
+
|
|
153
|
+
global_starts, global_ends = partition_coefficients(
|
|
154
|
+
domain_decomposition[i],
|
|
155
|
+
spaces[i],
|
|
156
|
+
min_blocks=[p+1 for p in pads])
|
|
157
|
+
|
|
158
|
+
new_cart = CartDecomposition(
|
|
159
|
+
domain_decomposition = domain_decomposition[i],
|
|
160
|
+
npts = npts,
|
|
161
|
+
global_starts = global_starts,
|
|
162
|
+
global_ends = global_ends,
|
|
163
|
+
pads = pads,
|
|
164
|
+
shifts = multiplicity)
|
|
165
|
+
|
|
166
|
+
carts.append(new_cart)
|
|
167
|
+
|
|
168
|
+
carts = tuple(carts)
|
|
169
|
+
|
|
170
|
+
return carts
|
|
171
|
+
|
|
172
|
+
#------------------------------------------------------------------------------
|
|
173
|
+
def construct_interface_spaces(domain_decomposition, g_spaces, carts, interiors, connectivity):
|
|
174
|
+
"""
|
|
175
|
+
Create the fem spaces for each interface in the domain given by the connectivity.
|
|
176
|
+
|
|
177
|
+
Parameters
|
|
178
|
+
----------
|
|
179
|
+
domain_decomposition : DomainDecomposition
|
|
180
|
+
|
|
181
|
+
g_spaces : dict
|
|
182
|
+
dictionary that contains the tensor-fem space for each patch.
|
|
183
|
+
|
|
184
|
+
cart: <CartDecomposition|MultiCartDecomposition>
|
|
185
|
+
Cartesian decomposition of the coefficient space.
|
|
186
|
+
|
|
187
|
+
interiors: list of Sympde.topology.Domain
|
|
188
|
+
List of the multipatch domain interiors.
|
|
189
|
+
|
|
190
|
+
connectivity: dict
|
|
191
|
+
The connectivity of the multipatch domain.
|
|
192
|
+
"""
|
|
193
|
+
if not connectivity:return
|
|
194
|
+
comm = domain_decomposition.comm
|
|
195
|
+
interfaces_cart = None
|
|
196
|
+
if comm is not None:
|
|
197
|
+
if connectivity:
|
|
198
|
+
communication_info = (get_minus_starts_ends, get_plus_starts_ends)
|
|
199
|
+
interfaces_cart = create_interfaces_cart(domain_decomposition, carts, connectivity.copy(), communication_info=communication_info)
|
|
200
|
+
|
|
201
|
+
for i,j in connectivity:
|
|
202
|
+
if comm is None:
|
|
203
|
+
cart_minus = carts[i]
|
|
204
|
+
cart_plus = carts[j]
|
|
205
|
+
else:
|
|
206
|
+
if not carts[i].is_comm_null and not carts[j].is_comm_null:
|
|
207
|
+
cart_minus = carts[i]
|
|
208
|
+
cart_plus = carts[j]
|
|
209
|
+
elif (i,j) in interfaces_cart:
|
|
210
|
+
cart_minus = interfaces_cart[i,j]
|
|
211
|
+
cart_plus = interfaces_cart[i,j]
|
|
212
|
+
else:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
((axis_minus, ext_minus), (axis_plus , ext_plus)) = connectivity[i, j]
|
|
216
|
+
|
|
217
|
+
g_spaces[interiors[i]].create_interface_space(axis_minus, ext_minus, cart=cart_minus)
|
|
218
|
+
g_spaces[interiors[j]].create_interface_space(axis_plus , ext_plus , cart=cart_plus)
|
|
219
|
+
|
|
220
|
+
max_ncells = tuple(max(ni,nj) for ni,nj in zip(g_spaces[interiors[i]].ncells,g_spaces[interiors[j]].ncells))
|
|
221
|
+
cart_minus = g_spaces[interiors[i]].get_refined_space(max_ncells).coeff_space.cart
|
|
222
|
+
cart_plus = g_spaces[interiors[j]].get_refined_space(max_ncells).coeff_space.cart
|
|
223
|
+
if isinstance(cart_minus, InterfaceCartDecomposition):
|
|
224
|
+
cart = InterfaceCartDecomposition(cart_minus._cart_minus, cart_plus._cart_plus,
|
|
225
|
+
cart_minus._comm, [axis_minus, axis_plus], [ext_minus, ext_plus],
|
|
226
|
+
[cart_minus.ranks_in_topo_minus, cart_plus.ranks_in_topo_plus],
|
|
227
|
+
[cart_minus._local_group_minus, cart_plus._local_group_plus],
|
|
228
|
+
[cart_minus._local_comm_minus, cart_plus._local_comm_plus],
|
|
229
|
+
[cart_minus._root_rank_minus, cart_plus._root_rank_plus],
|
|
230
|
+
[], reduce_elements=True)
|
|
231
|
+
|
|
232
|
+
cart_minus = cart
|
|
233
|
+
cart_plus = cart
|
|
234
|
+
cart.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
|
|
235
|
+
|
|
236
|
+
if any(nci!=ncj for nci,ncj in zip(max_ncells, g_spaces[interiors[i]].ncells)):
|
|
237
|
+
g_spaces[interiors[i]].get_refined_space(max_ncells).create_interface_space(axis_minus, ext_minus, cart=cart_minus)
|
|
238
|
+
g_spaces[interiors[j]].get_refined_space(max_ncells).create_interface_space(axis_plus , ext_plus , cart=cart_plus)
|
|
239
|
+
|
|
240
|
+
#------------------------------------------------------------------------------
|
|
241
|
+
def construct_reduced_interface_spaces(spaces, reduced_spaces, interiors, connectivity):
|
|
242
|
+
"""
|
|
243
|
+
Create the reduced spaces for the interface coefficients.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
|
|
248
|
+
spaces: dict
|
|
249
|
+
The tensor FEM spaces that we want to reduce for each patch.
|
|
250
|
+
|
|
251
|
+
reduced_spaces: dict
|
|
252
|
+
The reduced coefficient space for each patch.
|
|
253
|
+
|
|
254
|
+
interiors: list of Sympde.topology.Domain
|
|
255
|
+
The patches that construct the multipatch domain.
|
|
256
|
+
|
|
257
|
+
connectivity: dict
|
|
258
|
+
The connectivity of the multipatch domain.
|
|
259
|
+
|
|
260
|
+
"""
|
|
261
|
+
for i,j in connectivity:
|
|
262
|
+
((axis_i, ext_i), (axis_j , ext_j)) = connectivity[i, j]
|
|
263
|
+
|
|
264
|
+
patch_i = interiors[i]
|
|
265
|
+
patch_j = interiors[j]
|
|
266
|
+
space_i = spaces[patch_i].interfaces.get((axis_i, ext_i), None)
|
|
267
|
+
space_j = spaces[patch_j].interfaces.get((axis_j, ext_j), None)
|
|
268
|
+
|
|
269
|
+
if space_i is None or space_j is None: continue
|
|
270
|
+
|
|
271
|
+
cart_i = space_i.coeff_space.cart
|
|
272
|
+
cart_j = space_j.coeff_space.cart
|
|
273
|
+
|
|
274
|
+
if isinstance(cart_i, InterfaceCartDecomposition):
|
|
275
|
+
assert cart_i is cart_j
|
|
276
|
+
if isinstance(reduced_spaces[patch_i], VectorFemSpace):
|
|
277
|
+
for Vi,Vj in zip(reduced_spaces[patch_i].spaces, reduced_spaces[patch_j].spaces):
|
|
278
|
+
npts_i = [Vik.nbasis for Vik in Vi.spaces]
|
|
279
|
+
npts_j = [Vik.nbasis for Vik in Vj.spaces]
|
|
280
|
+
global_starts_i = Vi.coeff_space.cart.global_starts
|
|
281
|
+
global_starts_j = Vj.coeff_space.cart.global_starts
|
|
282
|
+
global_ends_i = Vi.coeff_space.cart.global_ends
|
|
283
|
+
global_ends_j = Vj.coeff_space.cart.global_ends
|
|
284
|
+
shifts_i = Vi.coeff_space.cart.shifts
|
|
285
|
+
shifts_j = Vj.coeff_space.cart.shifts
|
|
286
|
+
cart_ij = cart_i.reduce_npts(Vi.coeff_space.cart, Vj.coeff_space.cart)
|
|
287
|
+
cart_ij.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
|
|
288
|
+
Vi.create_interface_space(axis_i, ext_i, cart=cart_ij)
|
|
289
|
+
Vj.create_interface_space(axis_j, ext_j, cart=cart_ij)
|
|
290
|
+
|
|
291
|
+
cart = InterfaceCartDecomposition(cart_ij._cart_minus, cart_ij._cart_plus,
|
|
292
|
+
cart_ij._comm, [axis_i, axis_j], [ext_i, ext_j],
|
|
293
|
+
[cart_ij.ranks_in_topo_minus, cart_ij.ranks_in_topo_plus],
|
|
294
|
+
[cart_ij._local_group_minus, cart_ij._local_group_plus],
|
|
295
|
+
[cart_ij._local_comm_minus, cart_ij._local_comm_plus],
|
|
296
|
+
[cart_ij._root_rank_minus, cart_ij._root_rank_plus],
|
|
297
|
+
[], reduce_elements=True)
|
|
298
|
+
|
|
299
|
+
cart.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
|
|
300
|
+
max_ncells = tuple(max(ni,nj) for ni,nj in zip(Vi.ncells,Vj.ncells))
|
|
301
|
+
Vi.get_refined_space(max_ncells).create_interface_space(axis_i, ext_i, cart=cart)
|
|
302
|
+
Vj.get_refined_space(max_ncells).create_interface_space(axis_j, ext_j, cart=cart)
|
|
303
|
+
else:
|
|
304
|
+
Vi = reduced_spaces[patch_i]
|
|
305
|
+
Vj = reduced_spaces[patch_j]
|
|
306
|
+
npts_i = [Vik.nbasis for Vik in Vi.spaces]
|
|
307
|
+
npts_j = [Vik.nbasis for Vik in Vj.spaces]
|
|
308
|
+
cart_ij = cart_i.reduce_npts(Vi.coeff_space.cart, Vj.coeff_space.cart)
|
|
309
|
+
cart_ij.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
|
|
310
|
+
Vi.create_interface_space(axis_i, ext_i, cart=cart_ij)
|
|
311
|
+
Vj.create_interface_space(axis_j, ext_j, cart=cart_ij)
|
|
312
|
+
|
|
313
|
+
cart = InterfaceCartDecomposition(cart_ij._cart_minus, cart_ij._cart_plus,
|
|
314
|
+
cart_ij._comm, [axis_i, axis_j], [ext_i, ext_j],
|
|
315
|
+
[cart_ij.ranks_in_topo_minus, cart_ij.ranks_in_topo_plus],
|
|
316
|
+
[cart_ij._local_group_minus, cart_ij._local_group_plus],
|
|
317
|
+
[cart_ij._local_comm_minus, cart_ij._local_comm_plus],
|
|
318
|
+
[cart_ij._root_rank_minus, cart_ij._root_rank_plus],
|
|
319
|
+
[], reduce_elements=True)
|
|
320
|
+
|
|
321
|
+
cart.set_interface_communication_infos(get_minus_starts_ends, get_plus_starts_ends)
|
|
322
|
+
max_ncells = tuple(max(ni,nj) for ni,nj in zip(Vi.ncells,Vj.ncells))
|
|
323
|
+
Vi.get_refined_space(max_ncells).create_interface_space(axis_i, ext_i, cart=cart)
|
|
324
|
+
Vj.get_refined_space(max_ncells).create_interface_space(axis_j, ext_j, cart=cart)
|
|
325
|
+
else:
|
|
326
|
+
if isinstance(reduced_spaces[patch_i], VectorFemSpace):
|
|
327
|
+
for Vi,Vj in zip(reduced_spaces[patch_i].spaces, reduced_spaces[patch_j].spaces):
|
|
328
|
+
Vi.create_interface_space(axis_i, ext_i, cart=Vi.coeff_space.cart)
|
|
329
|
+
Vj.create_interface_space(axis_j , ext_j , cart=Vj.coeff_space.cart)
|
|
330
|
+
max_ncells = tuple(max(ni,nj) for ni,nj in zip(Vi.ncells,Vj.ncells))
|
|
331
|
+
cart_i = Vi.get_refined_space(max_ncells).coeff_space.cart
|
|
332
|
+
cart_j = Vj.get_refined_space(max_ncells).coeff_space.cart
|
|
333
|
+
Vi.get_refined_space(max_ncells).create_interface_space(axis_i, ext_i, cart=cart_i)
|
|
334
|
+
Vj.get_refined_space(max_ncells).create_interface_space(axis_j, ext_j, cart=cart_j)
|
|
335
|
+
else:
|
|
336
|
+
Vi = reduced_spaces[patch_i]
|
|
337
|
+
Vj = reduced_spaces[patch_j]
|
|
338
|
+
Vi.create_interface_space(axis_i, ext_i, cart=Vi.coeff_space.cart)
|
|
339
|
+
Vj.create_interface_space(axis_j , ext_j , cart=Vj.coeff_space.cart)
|
|
340
|
+
max_ncells = tuple(max(ni,nj) for ni,nj in zip(Vi.ncells,Vj.ncells))
|
|
341
|
+
cart_i = Vi.get_refined_space(max_ncells).coeff_space.cart
|
|
342
|
+
cart_j = Vj.get_refined_space(max_ncells).coeff_space.cart
|
|
343
|
+
Vi.get_refined_space(max_ncells).create_interface_space(axis_i, ext_i, cart=cart_i)
|
|
344
|
+
Vj.get_refined_space(max_ncells).create_interface_space(axis_j, ext_j, cart=cart_j)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from sympde.topology import element_of
|
|
4
|
+
from sympde.topology.space import ScalarFunction
|
|
5
|
+
from sympde.topology.mapping import Mapping
|
|
6
|
+
from sympde.calculus import dot
|
|
7
|
+
from sympde.expr.expr import LinearForm, integral
|
|
8
|
+
|
|
9
|
+
from feectools.api.settings import PSYDAC_BACKENDS
|
|
10
|
+
|
|
11
|
+
from feectools.linalg.kron import KroneckerDenseMatrix
|
|
12
|
+
from feectools.core.bsplines import hrefinement_matrix
|
|
13
|
+
from feectools.linalg.stencil import StencilVectorSpace
|
|
14
|
+
from feectools.fem.basic import FemSpace
|
|
15
|
+
|
|
16
|
+
__all__ = ('knots_to_insert', 'knot_insertion_projection_operator')
|
|
17
|
+
|
|
18
|
+
def knots_to_insert(coarse_grid, fine_grid, tol=1e-14):
|
|
19
|
+
""" Compute the point difference between the fine grid and coarse grid."""
|
|
20
|
+
# assert len(coarse_grid)*2-2 == len(fine_grid)-1
|
|
21
|
+
indices1 = (np.abs(fine_grid [:,None] - coarse_grid) < tol).any(0)
|
|
22
|
+
indices2 = ~(np.abs(coarse_grid[:,None] - fine_grid ) < tol).any(0)
|
|
23
|
+
|
|
24
|
+
intersection = coarse_grid[indices1]
|
|
25
|
+
T = fine_grid[indices2]
|
|
26
|
+
|
|
27
|
+
assert abs(intersection-coarse_grid).max()<tol
|
|
28
|
+
return T
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def knot_insertion_projection_operator(domain, codomain):
|
|
32
|
+
"""
|
|
33
|
+
Compute the projection operator based on the knot insertion technique.
|
|
34
|
+
|
|
35
|
+
Return a linear operator which projects an element of the domain to an
|
|
36
|
+
element of the codomain. Domain and codomain are scalar spline spaces over
|
|
37
|
+
a cuboid, built as the tensor product of 1D spline spaces. In particular,
|
|
38
|
+
domain and codomain have the same multi-degree (p1, p2, ...).
|
|
39
|
+
|
|
40
|
+
This function returns a LinearOperator K working at the level of the
|
|
41
|
+
spline coefficients, which are represented by StencilVector objects.
|
|
42
|
+
|
|
43
|
+
Thanks to the tensor-product structure of the spline spaces, the projection
|
|
44
|
+
operator is the Kronecker product of 1D projection operators K[i] operating
|
|
45
|
+
between 1D spaces. Each 1D operators is represented by a dense matrix:
|
|
46
|
+
|
|
47
|
+
K = K[0] x K[1] x ...
|
|
48
|
+
|
|
49
|
+
For each dimension i the 1D grids defined by the breakpoints of the two
|
|
50
|
+
spaces are assumed to be identical, or one nested into the other. Let nd[i]
|
|
51
|
+
and nc[i] be the number of cells along dimension i for domain and codomain,
|
|
52
|
+
respectively. We then have three different cases:
|
|
53
|
+
|
|
54
|
+
1. nd[i] == nc[i]:
|
|
55
|
+
The two 1D grids are assumed identical, and K[i] is the identity matrix.
|
|
56
|
+
|
|
57
|
+
2. nd[i] < nc[i]:
|
|
58
|
+
The 1D grid of the domain is assumed nested into the 1D grid of the
|
|
59
|
+
codomain, hence the 1D spline space of the domain is a subspace of the
|
|
60
|
+
1D spline space of the codomain. In this case we build K[i] using the
|
|
61
|
+
knot insertion algorithm.
|
|
62
|
+
|
|
63
|
+
3. nd[i] > nc[i]:
|
|
64
|
+
The 1D grid of the codomain is assumed nested into the 1D grid of the
|
|
65
|
+
domain, hence the 1D spline space of the codomain is a subspace of the
|
|
66
|
+
1D spline space of the domain. In this case we build K[i] as the
|
|
67
|
+
transpose of the matrix obtained using the knot insertion algorithm from
|
|
68
|
+
the codomain to the domain.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
domain : TensorFemSpace
|
|
73
|
+
Domain of the projection operator.
|
|
74
|
+
|
|
75
|
+
codomain : TensorFemSpace
|
|
76
|
+
Codomain of the projection operator.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
KroneckerDenseMatrix
|
|
81
|
+
Matrix representation of the projection operator. This is a
|
|
82
|
+
LinearOperator acting on the spline coefficients.
|
|
83
|
+
|
|
84
|
+
"""
|
|
85
|
+
ops = []
|
|
86
|
+
for d, c in zip(domain.spaces, codomain.spaces):
|
|
87
|
+
|
|
88
|
+
if d.ncells > c.ncells:
|
|
89
|
+
Ts = knots_to_insert(c.breaks, d.breaks)
|
|
90
|
+
P = hrefinement_matrix(Ts, c.degree, c.knots)
|
|
91
|
+
|
|
92
|
+
if d.basis == 'M':
|
|
93
|
+
assert c.basis == 'M'
|
|
94
|
+
P = np.diag(1 / d._scaling_array) @ P @ np.diag(c._scaling_array)
|
|
95
|
+
|
|
96
|
+
ops.append(P.T)
|
|
97
|
+
|
|
98
|
+
elif d.ncells < c.ncells:
|
|
99
|
+
Ts = knots_to_insert(d.breaks, c.breaks)
|
|
100
|
+
P = hrefinement_matrix(Ts, d.degree, d.knots)
|
|
101
|
+
|
|
102
|
+
if d.basis == 'M':
|
|
103
|
+
assert c.basis == 'M'
|
|
104
|
+
P = np.diag(1 / c._scaling_array) @ P @ np.diag(d._scaling_array)
|
|
105
|
+
|
|
106
|
+
ops.append(P)
|
|
107
|
+
|
|
108
|
+
else:
|
|
109
|
+
ops.append(np.eye(d.nbasis))
|
|
110
|
+
|
|
111
|
+
return KroneckerDenseMatrix(domain.coeff_space, codomain.coeff_space, *ops)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_dual_dofs(Vh, f, domain_h, backend_language="python", return_format='stencil_array'):
|
|
115
|
+
"""
|
|
116
|
+
return the dual dofs tilde_sigma_i(f) = < Lambda_i, f >_{L2} i = 1, .. dim(Vh)) of a given function f, as a stencil array or numpy array
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
Vh : FemSpace
|
|
121
|
+
The discrete space for the dual dofs
|
|
122
|
+
|
|
123
|
+
f : <sympy.Expr>
|
|
124
|
+
The function used for evaluation
|
|
125
|
+
|
|
126
|
+
domain_h :
|
|
127
|
+
The discrete domain corresponding to Vh
|
|
128
|
+
|
|
129
|
+
backend_language: <str>
|
|
130
|
+
The backend used to accelerate the code
|
|
131
|
+
|
|
132
|
+
return_format: <str>
|
|
133
|
+
The format of the dofs, can be 'stencil_array' or 'numpy_array'
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
tilde_f: <Vector|ndarray>
|
|
138
|
+
The dual dofs
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
from feectools.api.discretization import discretize
|
|
142
|
+
|
|
143
|
+
assert isinstance(Vh, FemSpace)
|
|
144
|
+
|
|
145
|
+
V = Vh.symbolic_space
|
|
146
|
+
v = element_of(V, name='v')
|
|
147
|
+
|
|
148
|
+
if Vh.is_vector_valued:
|
|
149
|
+
expr = dot(f,v)
|
|
150
|
+
else:
|
|
151
|
+
expr = f*v
|
|
152
|
+
|
|
153
|
+
l = LinearForm(v, integral( V.domain, expr))
|
|
154
|
+
lh = discretize(l, domain_h, Vh, backend=PSYDAC_BACKENDS[backend_language])
|
|
155
|
+
tilde_f = lh.assemble()
|
|
156
|
+
|
|
157
|
+
if return_format == 'numpy_array':
|
|
158
|
+
return tilde_f.toarray()
|
|
159
|
+
else:
|
|
160
|
+
return tilde_f
|