capytaine 2.3.1__cp314-cp314-win_amd64.whl → 3.0.0a1__cp314-cp314-win_amd64.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.
- capytaine/__about__.py +7 -2
- capytaine/__init__.py +11 -15
- capytaine/bem/engines.py +234 -354
- capytaine/bem/problems_and_results.py +14 -13
- capytaine/bem/solver.py +204 -80
- capytaine/bodies/bodies.py +278 -869
- capytaine/bodies/dofs.py +136 -9
- capytaine/bodies/hydrostatics.py +540 -0
- capytaine/bodies/multibodies.py +216 -0
- capytaine/green_functions/{libs/Delhommeau_float32.cp314-win_amd64.dll.a → Delhommeau_float32.cp314-win_amd64.dll.a} +0 -0
- capytaine/green_functions/Delhommeau_float32.cp314-win_amd64.pyd +0 -0
- capytaine/green_functions/{libs/Delhommeau_float64.cp314-win_amd64.dll.a → Delhommeau_float64.cp314-win_amd64.dll.a} +0 -0
- capytaine/green_functions/Delhommeau_float64.cp314-win_amd64.pyd +0 -0
- capytaine/green_functions/abstract_green_function.py +2 -2
- capytaine/green_functions/delhommeau.py +31 -16
- capytaine/green_functions/hams.py +19 -13
- capytaine/io/legacy.py +3 -103
- capytaine/io/xarray.py +11 -6
- capytaine/meshes/__init__.py +2 -6
- capytaine/meshes/abstract_meshes.py +375 -0
- capytaine/meshes/clean.py +302 -0
- capytaine/meshes/clip.py +347 -0
- capytaine/meshes/export.py +89 -0
- capytaine/meshes/geometry.py +244 -394
- capytaine/meshes/io.py +433 -0
- capytaine/meshes/meshes.py +617 -681
- capytaine/meshes/predefined/cylinders.py +22 -56
- capytaine/meshes/predefined/rectangles.py +26 -85
- capytaine/meshes/predefined/spheres.py +4 -11
- capytaine/meshes/quality.py +118 -407
- capytaine/meshes/surface_integrals.py +48 -29
- capytaine/meshes/symmetric_meshes.py +641 -0
- capytaine/meshes/visualization.py +353 -0
- capytaine/post_pro/free_surfaces.py +1 -4
- capytaine/post_pro/kochin.py +10 -10
- capytaine/tools/block_circulant_matrices.py +275 -0
- capytaine/tools/lists_of_points.py +2 -2
- capytaine/tools/memory_monitor.py +45 -0
- capytaine/tools/symbolic_multiplication.py +13 -1
- capytaine/tools/timer.py +58 -34
- capytaine-3.0.0a1.dist-info/DELVEWHEEL +2 -0
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +7 -2
- capytaine-3.0.0a1.dist-info/RECORD +70 -0
- capytaine/bodies/predefined/__init__.py +0 -6
- capytaine/bodies/predefined/cylinders.py +0 -151
- capytaine/bodies/predefined/rectangles.py +0 -111
- capytaine/bodies/predefined/spheres.py +0 -70
- capytaine/green_functions/FinGreen3D/.gitignore +0 -1
- capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +0 -3589
- capytaine/green_functions/FinGreen3D/LICENSE +0 -165
- capytaine/green_functions/FinGreen3D/Makefile +0 -16
- capytaine/green_functions/FinGreen3D/README.md +0 -24
- capytaine/green_functions/FinGreen3D/test_program.f90 +0 -39
- capytaine/green_functions/LiangWuNoblesse/.gitignore +0 -1
- capytaine/green_functions/LiangWuNoblesse/LICENSE +0 -504
- capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +0 -751
- capytaine/green_functions/LiangWuNoblesse/Makefile +0 -16
- capytaine/green_functions/LiangWuNoblesse/README.md +0 -2
- capytaine/green_functions/LiangWuNoblesse/test_program.f90 +0 -28
- capytaine/green_functions/libs/Delhommeau_float32.cp314-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp314-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/__init__.py +0 -0
- capytaine/io/mesh_loaders.py +0 -1086
- capytaine/io/mesh_writers.py +0 -692
- capytaine/io/meshio.py +0 -38
- capytaine/matrices/__init__.py +0 -16
- capytaine/matrices/block.py +0 -592
- capytaine/matrices/block_toeplitz.py +0 -325
- capytaine/matrices/builders.py +0 -89
- capytaine/matrices/linear_solvers.py +0 -232
- capytaine/matrices/low_rank.py +0 -395
- capytaine/meshes/clipper.py +0 -465
- capytaine/meshes/collections.py +0 -342
- capytaine/meshes/mesh_like_protocol.py +0 -37
- capytaine/meshes/properties.py +0 -276
- capytaine/meshes/quadratures.py +0 -80
- capytaine/meshes/symmetric.py +0 -462
- capytaine/tools/lru_cache.py +0 -49
- capytaine/ui/vtk/__init__.py +0 -3
- capytaine/ui/vtk/animation.py +0 -329
- capytaine/ui/vtk/body_viewer.py +0 -28
- capytaine/ui/vtk/helpers.py +0 -82
- capytaine/ui/vtk/mesh_viewer.py +0 -461
- capytaine-2.3.1.dist-info/DELVEWHEEL +0 -2
- capytaine-2.3.1.dist-info/RECORD +0 -97
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/entry_points.txt +0 -0
capytaine/bem/engines.py
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
"""Definition of the methods to build influence matrices, using possibly some sparse structures."""
|
|
2
2
|
# Copyright (C) 2017-2019 Matthieu Ancellin
|
|
3
|
-
# See LICENSE file at <https://github.com/
|
|
3
|
+
# See LICENSE file at <https://github.com/capytaine/capytaine>
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
from abc import ABC, abstractmethod
|
|
7
|
+
from typing import Tuple, Union, Optional, Callable
|
|
7
8
|
|
|
8
9
|
import numpy as np
|
|
9
|
-
|
|
10
|
-
from scipy.sparse import coo_matrix
|
|
11
|
-
from scipy.sparse import linalg as ssl
|
|
10
|
+
import scipy.sparse.linalg as ssl
|
|
12
11
|
|
|
13
|
-
from capytaine.meshes.
|
|
14
|
-
from capytaine.meshes.symmetric import ReflectionSymmetricMesh, TranslationalSymmetricMesh, AxialSymmetricMesh
|
|
12
|
+
from capytaine.meshes.symmetric_meshes import ReflectionSymmetricMesh, RotationSymmetricMesh
|
|
15
13
|
|
|
16
|
-
from capytaine.
|
|
17
|
-
from capytaine.
|
|
18
|
-
|
|
19
|
-
from capytaine.
|
|
20
|
-
|
|
14
|
+
from capytaine.green_functions.abstract_green_function import AbstractGreenFunction
|
|
15
|
+
from capytaine.green_functions.delhommeau import Delhommeau
|
|
16
|
+
|
|
17
|
+
from capytaine.tools.block_circulant_matrices import (
|
|
18
|
+
BlockCirculantMatrix, lu_decompose, has_been_lu_decomposed,
|
|
19
|
+
MatrixLike, LUDecomposedMatrixLike
|
|
20
|
+
)
|
|
21
21
|
|
|
22
22
|
LOG = logging.getLogger(__name__)
|
|
23
23
|
|
|
@@ -30,60 +30,93 @@ class MatrixEngine(ABC):
|
|
|
30
30
|
"""Abstract method to build a matrix."""
|
|
31
31
|
|
|
32
32
|
@abstractmethod
|
|
33
|
-
def build_matrices(self, mesh1, mesh2, free_surface, water_depth, wavenumber,
|
|
33
|
+
def build_matrices(self, mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer):
|
|
34
34
|
pass
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def build_S_matrix(self, mesh1, mesh2, free_surface, water_depth, wavenumber):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def build_fullK_matrix(self, mesh1, mesh2, free_surface, water_depth, wavenumber):
|
|
42
|
+
pass
|
|
40
43
|
|
|
41
44
|
|
|
42
45
|
##################
|
|
43
46
|
# BASIC ENGINE #
|
|
44
47
|
##################
|
|
45
48
|
|
|
49
|
+
class Counter:
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.nb_iter = 0
|
|
52
|
+
|
|
53
|
+
def __call__(self, *args, **kwargs):
|
|
54
|
+
self.nb_iter += 1
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def solve_gmres(A, b):
|
|
58
|
+
LOG.debug(f"Solve with GMRES for {A}.")
|
|
59
|
+
|
|
60
|
+
if LOG.isEnabledFor(logging.INFO):
|
|
61
|
+
counter = Counter()
|
|
62
|
+
x, info = ssl.gmres(A, b, atol=1e-6, callback=counter)
|
|
63
|
+
LOG.info(f"End of GMRES after {counter.nb_iter} iterations.")
|
|
64
|
+
|
|
65
|
+
else:
|
|
66
|
+
x, info = ssl.gmres(A, b, atol=1e-6)
|
|
67
|
+
|
|
68
|
+
if info > 0:
|
|
69
|
+
raise RuntimeError(f"No convergence of the GMRES after {info} iterations.\n"
|
|
70
|
+
"This can be due to overlapping panels or irregular frequencies.")
|
|
71
|
+
|
|
72
|
+
return x
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
LUDecomposedMatrixOrNot = Union[MatrixLike, LUDecomposedMatrixLike]
|
|
76
|
+
|
|
77
|
+
|
|
46
78
|
class BasicMatrixEngine(MatrixEngine):
|
|
47
79
|
"""
|
|
48
|
-
|
|
49
|
-
|
|
80
|
+
Default matrix engine.
|
|
81
|
+
|
|
82
|
+
Features:
|
|
83
|
+
- Caching of the last computed matrices.
|
|
84
|
+
- Supports plane symmetries and nested plane symmetries.
|
|
85
|
+
- Linear solver can be customized. Default is `lu_decomposition` with caching of the LU decomposition.
|
|
50
86
|
|
|
51
87
|
Parameters
|
|
52
88
|
----------
|
|
89
|
+
green_function: AbstractGreenFunction
|
|
90
|
+
the low level implementation used to compute the coefficients of the matrices.
|
|
53
91
|
linear_solver: str or function, optional
|
|
54
92
|
Setting of the numerical solver for linear problems Ax = b.
|
|
55
93
|
It can be set with the name of a preexisting solver
|
|
56
|
-
(available: "
|
|
94
|
+
(available: "lu_decomposition", "lu_decompositon_with_overwrite" and "gmres", the former is the default choice)
|
|
57
95
|
or by passing directly a solver function.
|
|
58
|
-
matrix_cache_size: int, optional
|
|
59
|
-
number of matrices to keep in cache
|
|
60
96
|
"""
|
|
61
97
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
98
|
+
green_function: AbstractGreenFunction
|
|
99
|
+
_linear_solver: Union[str, Callable]
|
|
100
|
+
last_computed_matrices: Optional[Tuple[MatrixLike, LUDecomposedMatrixOrNot]]
|
|
66
101
|
|
|
67
|
-
def __init__(self, *, linear_solver='lu_decomposition'
|
|
102
|
+
def __init__(self, *, green_function=None, linear_solver='lu_decomposition'):
|
|
68
103
|
|
|
69
|
-
if
|
|
70
|
-
self.linear_solver = self.available_linear_solvers[linear_solver]
|
|
71
|
-
else:
|
|
72
|
-
self.linear_solver = linear_solver
|
|
104
|
+
self.green_function = Delhommeau() if green_function is None else green_function
|
|
73
105
|
|
|
74
|
-
|
|
75
|
-
|
|
106
|
+
self._linear_solver = linear_solver
|
|
107
|
+
|
|
108
|
+
self.last_computed_inputs = None
|
|
109
|
+
self.last_computed_matrices = None
|
|
76
110
|
|
|
77
111
|
self.exportable_settings = {
|
|
78
112
|
'engine': 'BasicMatrixEngine',
|
|
79
|
-
'matrix_cache_size': matrix_cache_size,
|
|
80
113
|
'linear_solver': str(linear_solver),
|
|
114
|
+
**self.green_function.exportable_settings,
|
|
81
115
|
}
|
|
82
116
|
|
|
83
117
|
def __str__(self):
|
|
84
|
-
params = f"linear_solver
|
|
85
|
-
|
|
86
|
-
return f"BasicMatrixEngine({params})"
|
|
118
|
+
params= [f"green_function={self.green_function}", f"linear_solver={repr(self._linear_solver)}"]
|
|
119
|
+
return f"BasicMatrixEngine({', '.join(params)})"
|
|
87
120
|
|
|
88
121
|
def __repr__(self):
|
|
89
122
|
return self.__str__()
|
|
@@ -91,14 +124,83 @@ class BasicMatrixEngine(MatrixEngine):
|
|
|
91
124
|
def _repr_pretty_(self, p, cycle):
|
|
92
125
|
p.text(self.__str__())
|
|
93
126
|
|
|
94
|
-
def
|
|
127
|
+
def build_S_matrix(self, mesh1, mesh2, **gf_params) -> np.ndarray:
|
|
128
|
+
"""Similar to :code:`build_matrices`, but returning only :math:`S`"""
|
|
129
|
+
# Calls directly evaluate instead of build_matrices because the caching
|
|
130
|
+
# mechanism of build_matrices is not compatible with giving mesh1 as a
|
|
131
|
+
# list of points, but we need that for post-processing
|
|
132
|
+
S, _ = self.green_function.evaluate(mesh1, mesh2, **gf_params)
|
|
133
|
+
return S
|
|
134
|
+
|
|
135
|
+
def build_fullK_matrix(self, mesh1, mesh2, **gf_params) -> np.ndarray:
|
|
136
|
+
"""Similar to :code:`build_matrices`, but returning only full :math:`K`
|
|
137
|
+
(that is the three components of the gradient, not just the normal one)"""
|
|
138
|
+
# TODO: could use symmetries. In particular for forward, we compute the
|
|
139
|
+
# full velocity on the same mesh so symmetries could be used.
|
|
140
|
+
gf_params.setdefault("diagonal_term_in_double_layer", True)
|
|
141
|
+
gf_params.setdefault("adjoint_double_layer", True)
|
|
142
|
+
gf_params.setdefault("early_dot_product", False)
|
|
143
|
+
_, fullK = self.green_function.evaluate(mesh1, mesh2, **gf_params)
|
|
144
|
+
return fullK
|
|
145
|
+
|
|
146
|
+
def _build_matrices_with_symmetries(self, mesh1, mesh2, *, diagonal_term_in_double_layer=True, **gf_params) -> Tuple[MatrixLike, MatrixLike]:
|
|
147
|
+
if (isinstance(mesh1, ReflectionSymmetricMesh)
|
|
148
|
+
and isinstance(mesh2, ReflectionSymmetricMesh)
|
|
149
|
+
and mesh1.plane == mesh2.plane):
|
|
150
|
+
|
|
151
|
+
S_a, K_a = self._build_matrices_with_symmetries(mesh1.half, mesh2.half,
|
|
152
|
+
diagonal_term_in_double_layer=diagonal_term_in_double_layer, **gf_params)
|
|
153
|
+
S_b, K_b = self._build_matrices_with_symmetries(mesh1.other_half, mesh2.half,
|
|
154
|
+
diagonal_term_in_double_layer=False, **gf_params)
|
|
155
|
+
|
|
156
|
+
return BlockCirculantMatrix([S_a, S_b]), BlockCirculantMatrix([K_a, K_b])
|
|
157
|
+
|
|
158
|
+
elif (isinstance(mesh1, RotationSymmetricMesh)
|
|
159
|
+
and isinstance(mesh2, RotationSymmetricMesh)
|
|
160
|
+
and mesh1.n == mesh2.n):
|
|
161
|
+
|
|
162
|
+
S_cols, K_cols = self.green_function.evaluate(
|
|
163
|
+
mesh1.merged(), mesh2.wedge,
|
|
164
|
+
diagonal_term_in_double_layer=diagonal_term_in_double_layer,
|
|
165
|
+
**gf_params,
|
|
166
|
+
)
|
|
167
|
+
# Building the first column of blocks, that is the interactions of all of mesh1 with the reference wedge of mesh2.
|
|
168
|
+
|
|
169
|
+
n_blocks = mesh1.n # == mesh2.n
|
|
170
|
+
block_shape = (mesh2.wedge.nb_faces, mesh2.wedge.nb_faces)
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
BlockCirculantMatrix(S_cols.reshape((n_blocks, *block_shape))),
|
|
174
|
+
BlockCirculantMatrix(K_cols.reshape((n_blocks, *block_shape))),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
gf_params.setdefault("early_dot_product", True)
|
|
179
|
+
return self.green_function.evaluate(mesh1, mesh2, diagonal_term_in_double_layer=diagonal_term_in_double_layer, **gf_params)
|
|
180
|
+
|
|
181
|
+
def _build_and_cache_matrices_with_symmetries(
|
|
182
|
+
self, mesh1, mesh2, **gf_params
|
|
183
|
+
) -> Tuple[MatrixLike, LUDecomposedMatrixOrNot]:
|
|
184
|
+
if (mesh1, mesh2, gf_params) == self.last_computed_inputs:
|
|
185
|
+
LOG.debug("%s: reading cache.", self.__class__.__name__)
|
|
186
|
+
return self.last_computed_matrices
|
|
187
|
+
else:
|
|
188
|
+
LOG.debug("%s: computing new matrices.", self.__class__.__name__)
|
|
189
|
+
self.last_computed_matrices = None # Unlink former cached values, so the memory can be freed to compute new matrices.
|
|
190
|
+
S, K = self._build_matrices_with_symmetries(mesh1, mesh2, **gf_params)
|
|
191
|
+
self.last_computed_inputs = (mesh1, mesh2, gf_params)
|
|
192
|
+
self.last_computed_matrices = (S, K)
|
|
193
|
+
return self.last_computed_matrices
|
|
194
|
+
|
|
195
|
+
# Main interface for compliance with AbstractGreenFunction interface
|
|
196
|
+
def build_matrices(self, mesh1, mesh2, **gf_params):
|
|
95
197
|
r"""Build the influence matrices between mesh1 and mesh2.
|
|
96
198
|
|
|
97
199
|
Parameters
|
|
98
200
|
----------
|
|
99
|
-
mesh1:
|
|
201
|
+
mesh1: MeshLike or list of points
|
|
100
202
|
mesh of the receiving body (where the potential is measured)
|
|
101
|
-
mesh2:
|
|
203
|
+
mesh2: MeshLike
|
|
102
204
|
mesh of the source body (over which the source distribution is integrated)
|
|
103
205
|
free_surface: float
|
|
104
206
|
position of the free surface (default: :math:`z = 0`)
|
|
@@ -106,336 +208,114 @@ class BasicMatrixEngine(MatrixEngine):
|
|
|
106
208
|
position of the sea bottom (default: :math:`z = -\infty`)
|
|
107
209
|
wavenumber: float
|
|
108
210
|
wavenumber (default: 1.0)
|
|
109
|
-
green_function: AbstractGreenFunction
|
|
110
|
-
object with an "evaluate" method that computes the Green function.
|
|
111
211
|
adjoint_double_layer: bool, optional
|
|
112
212
|
compute double layer for direct method (F) or adjoint double layer for indirect method (T) matrices (default: True)
|
|
113
213
|
|
|
114
214
|
Returns
|
|
115
215
|
-------
|
|
116
|
-
tuple of matrix-like
|
|
216
|
+
tuple of matrix-like (Numpy arrays or BlockCirculantMatrix)
|
|
117
217
|
the matrices :math:`S` and :math:`K`
|
|
118
218
|
"""
|
|
219
|
+
return self._build_and_cache_matrices_with_symmetries(
|
|
220
|
+
mesh1, mesh2, **gf_params
|
|
221
|
+
)
|
|
119
222
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
and mesh1.plane == mesh2.plane):
|
|
223
|
+
def linear_solver(self, A: LUDecomposedMatrixOrNot, b: np.ndarray) -> np.ndarray:
|
|
224
|
+
"""Solve a linear system with left-hand side A and right-hand-side b
|
|
123
225
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
226
|
+
Parameters
|
|
227
|
+
----------
|
|
228
|
+
A: matrix-like
|
|
229
|
+
Expected to be the second output of `build_matrices`
|
|
230
|
+
b: np.ndarray
|
|
231
|
+
Vector of the correct length
|
|
130
232
|
|
|
131
|
-
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
x: np.ndarray
|
|
236
|
+
Vector such that A@x = b
|
|
237
|
+
"""
|
|
238
|
+
if not isinstance(self._linear_solver, str):
|
|
239
|
+
# If not a string, it is expected to be a custom function that can
|
|
240
|
+
# be called to solve the system
|
|
241
|
+
x = self._linear_solver(A, b)
|
|
242
|
+
|
|
243
|
+
if not x.shape == b.shape:
|
|
244
|
+
raise ValueError(f"Error in linear solver of {self}: the shape of the output ({x.shape}) "
|
|
245
|
+
f"does not match the expected shape ({b.shape})")
|
|
246
|
+
|
|
247
|
+
return x
|
|
248
|
+
|
|
249
|
+
elif self._linear_solver in ("lu_decomposition", "lu_decomposition_with_overwrite") :
|
|
250
|
+
overwrite_a = (self._linear_solver == "lu_decomposition_with_overwrite")
|
|
251
|
+
if not has_been_lu_decomposed(A):
|
|
252
|
+
luA = lu_decompose(A, overwrite_a=overwrite_a)
|
|
253
|
+
if A is self.last_computed_matrices[1]:
|
|
254
|
+
# In normal operation of Capytaine, `A` is always the $D$
|
|
255
|
+
# or $K$ matrix stored in the cache of the solver.
|
|
256
|
+
# Here we replace the matrix by its LU decomposition in the
|
|
257
|
+
# cache to avoid doing the decomposition again.
|
|
258
|
+
self.last_computed_matrices = (self.last_computed_matrices[0], luA)
|
|
259
|
+
else:
|
|
260
|
+
luA: LUDecomposedMatrixLike = A
|
|
261
|
+
return luA.solve(b)
|
|
262
|
+
|
|
263
|
+
elif self._linear_solver == "gmres":
|
|
264
|
+
return solve_gmres(A, b)
|
|
132
265
|
|
|
133
266
|
else:
|
|
134
|
-
|
|
135
|
-
|
|
267
|
+
raise NotImplementedError(
|
|
268
|
+
f"Unknown `linear_solver` in BasicMatrixEngine: {self._linear_solver}"
|
|
136
269
|
)
|
|
137
270
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
271
|
+
def compute_ram_estimation(self, problem):
|
|
272
|
+
nb_faces = problem.body.mesh.nb_faces
|
|
273
|
+
nb_matrices = 2
|
|
274
|
+
nb_bytes = 16
|
|
275
|
+
|
|
276
|
+
if self._linear_solver == "lu_decomposition":
|
|
277
|
+
nb_matrices += 1
|
|
278
|
+
|
|
279
|
+
if self.green_function.floating_point_precision == "float32":
|
|
280
|
+
nb_bytes = 8
|
|
281
|
+
|
|
282
|
+
# In theory a simple symmetry is a gain of factor 1/2
|
|
283
|
+
# and a nested symmetry is a gain of factor 1/4.
|
|
284
|
+
# For the solvers that use LU decomposition the gain is a bit less.
|
|
285
|
+
solver_factors = {
|
|
286
|
+
# Formula to compute the factor of gain:
|
|
287
|
+
# (2 matrices * theoretical symmetry factor + LU decomposition + intermediate_step) / nb matrices without symmetry
|
|
288
|
+
"lu_decomposition": {
|
|
289
|
+
"simple": 2 / 3, # (2 * 1/2 + 1/2 + 1/2) / 3
|
|
290
|
+
"nested": 5 / 12, # (2 * 1/4 + 1/4 + 1/2) / 3
|
|
291
|
+
"rotation": 4 / 3,
|
|
292
|
+
},
|
|
293
|
+
# Formula to compute the factor of gain:
|
|
294
|
+
# (2 matrices * theoretical symmetry factor + intermediate step) / nb matrices without symmetry
|
|
295
|
+
"lu_decomposition_with_overwrite": {
|
|
296
|
+
"simple": 3 / 4, # (2 * 1/2 + 1/2) / 2
|
|
297
|
+
"nested": 1 / 2, # (2 * 1/4 + 1/2) / 2
|
|
298
|
+
"rotation": 3 / 2,
|
|
299
|
+
},
|
|
300
|
+
"gmres": {
|
|
301
|
+
"simple": 1 / 2,
|
|
302
|
+
"nested": 1 / 4,
|
|
303
|
+
"rotation": 1,
|
|
304
|
+
},
|
|
171
305
|
}
|
|
172
306
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def build_matrices(self,
|
|
184
|
-
mesh1, mesh2, free_surface, water_depth, wavenumber, green_function,
|
|
185
|
-
adjoint_double_layer=True):
|
|
186
|
-
|
|
187
|
-
return self._build_matrices(
|
|
188
|
-
mesh1, mesh2, free_surface, water_depth, wavenumber, green_function,
|
|
189
|
-
adjoint_double_layer, _rec_depth=1)
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
def _build_matrices(self,
|
|
193
|
-
mesh1, mesh2, free_surface, water_depth, wavenumber, green_function,
|
|
194
|
-
adjoint_double_layer, _rec_depth=1):
|
|
195
|
-
"""Recursively builds a hierarchical matrix between mesh1 and mesh2.
|
|
196
|
-
|
|
197
|
-
Same arguments as :func:`BasicMatrixEngine.build_matrices`.
|
|
198
|
-
|
|
199
|
-
:code:`_rec_depth` keeps track of the recursion depth only for pretty log printing.
|
|
200
|
-
"""
|
|
201
|
-
|
|
202
|
-
if logging.getLogger().isEnabledFor(logging.DEBUG):
|
|
203
|
-
log_entry = (
|
|
204
|
-
"\t" * (_rec_depth+1) +
|
|
205
|
-
"Build the S and K influence matrices between {mesh1} and {mesh2}"
|
|
206
|
-
.format(mesh1=mesh1.name, mesh2=(mesh2.name if mesh2 is not mesh1 else 'itself'))
|
|
207
|
-
)
|
|
208
|
-
else:
|
|
209
|
-
log_entry = "" # will not be used
|
|
210
|
-
|
|
211
|
-
# Distance between the meshes (for ACA).
|
|
212
|
-
distance = np.linalg.norm(mesh1.center_of_mass_of_nodes - mesh2.center_of_mass_of_nodes)
|
|
213
|
-
|
|
214
|
-
# I) SPARSE COMPUTATION
|
|
215
|
-
# I-i) BLOCK TOEPLITZ MATRIX
|
|
216
|
-
|
|
217
|
-
if (isinstance(mesh1, ReflectionSymmetricMesh)
|
|
218
|
-
and isinstance(mesh2, ReflectionSymmetricMesh)
|
|
219
|
-
and mesh1.plane == mesh2.plane):
|
|
220
|
-
|
|
221
|
-
LOG.debug(log_entry + " using mirror symmetry.")
|
|
222
|
-
|
|
223
|
-
S_a, V_a = self._build_matrices(
|
|
224
|
-
mesh1[0], mesh2[0], free_surface, water_depth, wavenumber, green_function,
|
|
225
|
-
adjoint_double_layer=adjoint_double_layer, _rec_depth=_rec_depth+1)
|
|
226
|
-
S_b, V_b = self._build_matrices(
|
|
227
|
-
mesh1[0], mesh2[1], free_surface, water_depth, wavenumber, green_function,
|
|
228
|
-
adjoint_double_layer=adjoint_double_layer, _rec_depth=_rec_depth+1)
|
|
229
|
-
|
|
230
|
-
return BlockSymmetricToeplitzMatrix([[S_a, S_b]]), BlockSymmetricToeplitzMatrix([[V_a, V_b]])
|
|
231
|
-
|
|
232
|
-
elif (isinstance(mesh1, TranslationalSymmetricMesh)
|
|
233
|
-
and isinstance(mesh2, TranslationalSymmetricMesh)
|
|
234
|
-
and np.allclose(mesh1.translation, mesh2.translation)
|
|
235
|
-
and mesh1.nb_submeshes == mesh2.nb_submeshes):
|
|
236
|
-
|
|
237
|
-
LOG.debug(log_entry + " using translational symmetry.")
|
|
238
|
-
|
|
239
|
-
S_list, V_list = [], []
|
|
240
|
-
for submesh in mesh2:
|
|
241
|
-
S, V = self._build_matrices(
|
|
242
|
-
mesh1[0], submesh, free_surface, water_depth, wavenumber, green_function,
|
|
243
|
-
adjoint_double_layer=adjoint_double_layer, _rec_depth=_rec_depth+1)
|
|
244
|
-
S_list.append(S)
|
|
245
|
-
V_list.append(V)
|
|
246
|
-
for submesh in mesh1[1:][::-1]:
|
|
247
|
-
S, V = self._build_matrices(
|
|
248
|
-
submesh, mesh2[0], free_surface, water_depth, wavenumber, green_function,
|
|
249
|
-
adjoint_double_layer=adjoint_double_layer, _rec_depth=_rec_depth+1)
|
|
250
|
-
S_list.append(S)
|
|
251
|
-
V_list.append(V)
|
|
252
|
-
|
|
253
|
-
return BlockToeplitzMatrix([S_list]), BlockToeplitzMatrix([V_list])
|
|
254
|
-
|
|
255
|
-
elif (isinstance(mesh1, AxialSymmetricMesh)
|
|
256
|
-
and isinstance(mesh2, AxialSymmetricMesh)
|
|
257
|
-
and mesh1.axis == mesh2.axis
|
|
258
|
-
and mesh1.nb_submeshes == mesh2.nb_submeshes):
|
|
259
|
-
|
|
260
|
-
LOG.debug(log_entry + " using rotation symmetry.")
|
|
261
|
-
|
|
262
|
-
S_line, V_line = [], []
|
|
263
|
-
for submesh in mesh2[:mesh2.nb_submeshes]:
|
|
264
|
-
S, V = self._build_matrices(
|
|
265
|
-
mesh1[0], submesh, free_surface, water_depth, wavenumber, green_function,
|
|
266
|
-
adjoint_double_layer=adjoint_double_layer, _rec_depth=_rec_depth+1)
|
|
267
|
-
S_line.append(S)
|
|
268
|
-
V_line.append(V)
|
|
269
|
-
|
|
270
|
-
return BlockCirculantMatrix([S_line]), BlockCirculantMatrix([V_line])
|
|
271
|
-
|
|
272
|
-
# I-ii) LOW-RANK MATRIX WITH ACA
|
|
273
|
-
|
|
274
|
-
elif distance > self.ACA_distance*mesh1.diameter_of_nodes or distance > self.ACA_distance*mesh2.diameter_of_nodes:
|
|
275
|
-
|
|
276
|
-
LOG.debug(log_entry + " using ACA.")
|
|
277
|
-
|
|
278
|
-
def get_row_func(i):
|
|
279
|
-
s, v = green_function.evaluate(
|
|
280
|
-
mesh1.extract_one_face(i), mesh2,
|
|
281
|
-
free_surface, water_depth, wavenumber,
|
|
282
|
-
adjoint_double_layer=adjoint_double_layer
|
|
283
|
-
)
|
|
284
|
-
return s.flatten(), v.flatten()
|
|
285
|
-
|
|
286
|
-
def get_col_func(j):
|
|
287
|
-
s, v = green_function.evaluate(
|
|
288
|
-
mesh1, mesh2.extract_one_face(j),
|
|
289
|
-
free_surface, water_depth, wavenumber,
|
|
290
|
-
adjoint_double_layer=adjoint_double_layer
|
|
291
|
-
)
|
|
292
|
-
return s.flatten(), v.flatten()
|
|
293
|
-
|
|
294
|
-
try:
|
|
295
|
-
return LowRankMatrix.from_rows_and_cols_functions_with_multi_ACA(
|
|
296
|
-
get_row_func, get_col_func, mesh1.nb_faces, mesh2.nb_faces,
|
|
297
|
-
nb_matrices=2, id_main=1, # Approximate V and get an approximation of S at the same time
|
|
298
|
-
tol=self.ACA_tol, dtype=np.complex128)
|
|
299
|
-
except NoConvergenceOfACA:
|
|
300
|
-
pass # Continue with non sparse computation
|
|
301
|
-
|
|
302
|
-
# II) NON-SPARSE COMPUTATIONS
|
|
303
|
-
# II-i) BLOCK MATRIX
|
|
304
|
-
|
|
305
|
-
if (isinstance(mesh1, CollectionOfMeshes)
|
|
306
|
-
and isinstance(mesh2, CollectionOfMeshes)):
|
|
307
|
-
|
|
308
|
-
LOG.debug(log_entry + " using block matrix structure.")
|
|
309
|
-
|
|
310
|
-
S_matrix, V_matrix = [], []
|
|
311
|
-
for submesh1 in mesh1:
|
|
312
|
-
S_line, V_line = [], []
|
|
313
|
-
for submesh2 in mesh2:
|
|
314
|
-
S, V = self._build_matrices(
|
|
315
|
-
submesh1, submesh2, free_surface, water_depth, wavenumber, green_function,
|
|
316
|
-
adjoint_double_layer=adjoint_double_layer, _rec_depth=_rec_depth+1)
|
|
317
|
-
|
|
318
|
-
S_line.append(S)
|
|
319
|
-
V_line.append(V)
|
|
320
|
-
S_matrix.append(S_line)
|
|
321
|
-
V_matrix.append(V_line)
|
|
322
|
-
|
|
323
|
-
return BlockMatrix(S_matrix), BlockMatrix(V_matrix)
|
|
324
|
-
|
|
325
|
-
# II-ii) PLAIN NUMPY ARRAY
|
|
326
|
-
|
|
307
|
+
if isinstance(problem.body.mesh, ReflectionSymmetricMesh):
|
|
308
|
+
if isinstance(problem.body.mesh.half, ReflectionSymmetricMesh):
|
|
309
|
+
# Should not go deeper than that, there is currently only two
|
|
310
|
+
# symmetries available
|
|
311
|
+
symmetry_type = "nested"
|
|
312
|
+
else:
|
|
313
|
+
symmetry_type = "simple"
|
|
314
|
+
symmetry_factor = solver_factors[self._linear_solver][symmetry_type]
|
|
315
|
+
elif isinstance(problem.body.mesh, RotationSymmetricMesh):
|
|
316
|
+
symmetry_factor = solver_factors[self._linear_solver]["rotation"] / problem.body.mesh.n
|
|
327
317
|
else:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
S, V = green_function.evaluate(
|
|
331
|
-
mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer=adjoint_double_layer
|
|
332
|
-
)
|
|
333
|
-
return S, V
|
|
318
|
+
symmetry_factor = 1.0
|
|
334
319
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
some block-Toeplitz structure.
|
|
338
|
-
|
|
339
|
-
Parameters
|
|
340
|
-
----------
|
|
341
|
-
ACA_distance: float, optional
|
|
342
|
-
Above this distance, the ACA is used to approximate the matrix with a low-rank block.
|
|
343
|
-
ACA_tol: float, optional
|
|
344
|
-
The tolerance of the ACA when building a low-rank matrix.
|
|
345
|
-
matrix_cache_size: int, optional
|
|
346
|
-
number of matrices to keep in cache
|
|
347
|
-
"""
|
|
348
|
-
|
|
349
|
-
def __init__(self, *, ACA_distance=8.0, ACA_tol=1e-2, matrix_cache_size=1):
|
|
350
|
-
super().__init__(ACA_distance=ACA_distance, ACA_tol=ACA_tol, matrix_cache_size=matrix_cache_size)
|
|
351
|
-
self.linear_solver = linear_solvers.solve_precond_gmres
|
|
352
|
-
|
|
353
|
-
def build_matrices(self,
|
|
354
|
-
mesh1, mesh2, free_surface, water_depth, wavenumber,
|
|
355
|
-
green_function, adjoint_double_layer=True):
|
|
356
|
-
"""Recursively builds a hierarchical matrix between mesh1 and mesh2,
|
|
357
|
-
and precomputes some of the quantities needed for the preconditioner.
|
|
358
|
-
|
|
359
|
-
Same arguments as :func:`BasicMatrixEngine.build_matrices`, except for rec_depth
|
|
360
|
-
"""
|
|
361
|
-
# Build the matrices using the method of the parent class
|
|
362
|
-
S, K = super().build_matrices(mesh1, mesh2, free_surface, water_depth,
|
|
363
|
-
wavenumber, green_function,
|
|
364
|
-
adjoint_double_layer=adjoint_double_layer)
|
|
365
|
-
|
|
366
|
-
path_to_leaf = mesh1.path_to_leaf()
|
|
367
|
-
|
|
368
|
-
n = len(path_to_leaf)
|
|
369
|
-
N = K.shape[0]
|
|
370
|
-
|
|
371
|
-
# Navigate to the diagonal blocks and compute their LU decompositions
|
|
372
|
-
DLU = []
|
|
373
|
-
diag_shapes = []
|
|
374
|
-
for leaf in range(n):
|
|
375
|
-
# Navigate to the block containing the one we need
|
|
376
|
-
# (one layer above in the dendrogram)
|
|
377
|
-
#upper_block = self.access_block_by_path(K, path_to_leaf[leaf][:-1])
|
|
378
|
-
upper_block = K.access_block_by_path(path_to_leaf[leaf][:-1])
|
|
379
|
-
# find the local index in the full path
|
|
380
|
-
ind = path_to_leaf[leaf][-1]
|
|
381
|
-
# compute the LU decomposition and add to the list
|
|
382
|
-
DLU.append(lu_factor(upper_block.all_blocks[ind, ind]))
|
|
383
|
-
diag_shapes.append(upper_block.all_blocks[ind, ind].shape[0])
|
|
384
|
-
|
|
385
|
-
# Build the restriction and precompute its multiplication by K
|
|
386
|
-
R = np.zeros((n, N), dtype=complex)
|
|
387
|
-
RA = np.zeros((n, N), dtype=complex)
|
|
388
|
-
for ii in range(n):
|
|
389
|
-
row_slice = slice(sum(diag_shapes[:ii]), sum(diag_shapes[:ii+1]))
|
|
390
|
-
R[ii, row_slice] = 1
|
|
391
|
-
# Compute the multiplication using only the relevant slices of K
|
|
392
|
-
# The slices are found by navigating the tree
|
|
393
|
-
#RA[ii, :] = self.slice_rmatvec(R[ii, :], ii)
|
|
394
|
-
Aloc = K
|
|
395
|
-
v = R[ii, :]
|
|
396
|
-
va = np.zeros(N, dtype=complex)
|
|
397
|
-
free = [0, N]
|
|
398
|
-
|
|
399
|
-
for lvl, jj in enumerate(path_to_leaf[ii]):
|
|
400
|
-
|
|
401
|
-
Nrows = Aloc.all_blocks[jj, jj].shape[0]
|
|
402
|
-
|
|
403
|
-
if jj==0:
|
|
404
|
-
v = v[:Nrows]
|
|
405
|
-
w = v @ Aloc.all_blocks[0,1]
|
|
406
|
-
va[free[1]-len(w) : free[1]] = w
|
|
407
|
-
free[1] = free[1] - len(w)
|
|
408
|
-
else:
|
|
409
|
-
v = v[-Nrows:]
|
|
410
|
-
w = v @ Aloc.all_blocks[1, 0]
|
|
411
|
-
va[free[0] : free[0]+len(w)] = w
|
|
412
|
-
free[0] = free[0] + len(w)
|
|
413
|
-
|
|
414
|
-
Aloc = Aloc.all_blocks[jj, jj]
|
|
415
|
-
|
|
416
|
-
if lvl == len(path_to_leaf[ii])-1:
|
|
417
|
-
w = v@Aloc
|
|
418
|
-
va[free[0] : free[1]] = w
|
|
419
|
-
free[0] = free[0] + len(w)
|
|
420
|
-
|
|
421
|
-
RA[ii, :] = va
|
|
422
|
-
|
|
423
|
-
Ac = RA @ R.T
|
|
424
|
-
AcLU = lu_factor(Ac)
|
|
425
|
-
|
|
426
|
-
# Now navigate again to the diagonal blocks and set them to zero
|
|
427
|
-
for leaf in range(n):
|
|
428
|
-
upper_block = K.access_block_by_path(path_to_leaf[leaf][:-1])
|
|
429
|
-
ind = path_to_leaf[leaf][-1]
|
|
430
|
-
# turn the diagonal block into a zero sparse matrix
|
|
431
|
-
upper_block.all_blocks[ind, ind] = coo_matrix(upper_block.all_blocks[ind, ind].shape)
|
|
432
|
-
|
|
433
|
-
def PinvA_mv(v):
|
|
434
|
-
v = v + 1j*np.zeros(N)
|
|
435
|
-
return v - linear_solvers._block_Jacobi_coarse_corr(
|
|
436
|
-
K, np.zeros(N, dtype=complex), v,
|
|
437
|
-
R, RA, AcLU, DLU, diag_shapes, n)
|
|
438
|
-
|
|
439
|
-
PinvA = ssl.LinearOperator((N, N), matvec=PinvA_mv)
|
|
440
|
-
|
|
441
|
-
return S, (K, R, RA, AcLU, DLU, diag_shapes, n, PinvA)
|
|
320
|
+
memory_peak = symmetry_factor * nb_faces**2 * nb_matrices * nb_bytes/1e9
|
|
321
|
+
return memory_peak
|