capytaine 2.3.1__cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. capytaine/__about__.py +16 -0
  2. capytaine/__init__.py +36 -0
  3. capytaine/bem/__init__.py +0 -0
  4. capytaine/bem/airy_waves.py +111 -0
  5. capytaine/bem/engines.py +441 -0
  6. capytaine/bem/problems_and_results.py +600 -0
  7. capytaine/bem/solver.py +594 -0
  8. capytaine/bodies/__init__.py +4 -0
  9. capytaine/bodies/bodies.py +1221 -0
  10. capytaine/bodies/dofs.py +19 -0
  11. capytaine/bodies/predefined/__init__.py +6 -0
  12. capytaine/bodies/predefined/cylinders.py +151 -0
  13. capytaine/bodies/predefined/rectangles.py +111 -0
  14. capytaine/bodies/predefined/spheres.py +70 -0
  15. capytaine/green_functions/FinGreen3D/.gitignore +1 -0
  16. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +3589 -0
  17. capytaine/green_functions/FinGreen3D/LICENSE +165 -0
  18. capytaine/green_functions/FinGreen3D/Makefile +16 -0
  19. capytaine/green_functions/FinGreen3D/README.md +24 -0
  20. capytaine/green_functions/FinGreen3D/test_program.f90 +39 -0
  21. capytaine/green_functions/LiangWuNoblesse/.gitignore +1 -0
  22. capytaine/green_functions/LiangWuNoblesse/LICENSE +504 -0
  23. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +751 -0
  24. capytaine/green_functions/LiangWuNoblesse/Makefile +16 -0
  25. capytaine/green_functions/LiangWuNoblesse/README.md +2 -0
  26. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +28 -0
  27. capytaine/green_functions/__init__.py +2 -0
  28. capytaine/green_functions/abstract_green_function.py +64 -0
  29. capytaine/green_functions/delhommeau.py +507 -0
  30. capytaine/green_functions/hams.py +204 -0
  31. capytaine/green_functions/libs/Delhommeau_float32.cpython-314t-x86_64-linux-gnu.so +0 -0
  32. capytaine/green_functions/libs/Delhommeau_float64.cpython-314t-x86_64-linux-gnu.so +0 -0
  33. capytaine/green_functions/libs/__init__.py +0 -0
  34. capytaine/io/__init__.py +0 -0
  35. capytaine/io/bemio.py +153 -0
  36. capytaine/io/legacy.py +328 -0
  37. capytaine/io/mesh_loaders.py +1086 -0
  38. capytaine/io/mesh_writers.py +692 -0
  39. capytaine/io/meshio.py +38 -0
  40. capytaine/io/wamit.py +479 -0
  41. capytaine/io/xarray.py +668 -0
  42. capytaine/matrices/__init__.py +16 -0
  43. capytaine/matrices/block.py +592 -0
  44. capytaine/matrices/block_toeplitz.py +325 -0
  45. capytaine/matrices/builders.py +89 -0
  46. capytaine/matrices/linear_solvers.py +232 -0
  47. capytaine/matrices/low_rank.py +395 -0
  48. capytaine/meshes/__init__.py +6 -0
  49. capytaine/meshes/clipper.py +465 -0
  50. capytaine/meshes/collections.py +342 -0
  51. capytaine/meshes/geometry.py +409 -0
  52. capytaine/meshes/mesh_like_protocol.py +37 -0
  53. capytaine/meshes/meshes.py +890 -0
  54. capytaine/meshes/predefined/__init__.py +6 -0
  55. capytaine/meshes/predefined/cylinders.py +314 -0
  56. capytaine/meshes/predefined/rectangles.py +261 -0
  57. capytaine/meshes/predefined/spheres.py +62 -0
  58. capytaine/meshes/properties.py +276 -0
  59. capytaine/meshes/quadratures.py +80 -0
  60. capytaine/meshes/quality.py +448 -0
  61. capytaine/meshes/surface_integrals.py +63 -0
  62. capytaine/meshes/symmetric.py +462 -0
  63. capytaine/post_pro/__init__.py +6 -0
  64. capytaine/post_pro/free_surfaces.py +88 -0
  65. capytaine/post_pro/impedance.py +92 -0
  66. capytaine/post_pro/kochin.py +54 -0
  67. capytaine/post_pro/rao.py +60 -0
  68. capytaine/tools/__init__.py +0 -0
  69. capytaine/tools/cache_on_disk.py +26 -0
  70. capytaine/tools/deprecation_handling.py +18 -0
  71. capytaine/tools/lists_of_points.py +52 -0
  72. capytaine/tools/lru_cache.py +49 -0
  73. capytaine/tools/optional_imports.py +27 -0
  74. capytaine/tools/prony_decomposition.py +150 -0
  75. capytaine/tools/symbolic_multiplication.py +149 -0
  76. capytaine/tools/timer.py +66 -0
  77. capytaine/ui/__init__.py +0 -0
  78. capytaine/ui/cli.py +28 -0
  79. capytaine/ui/rich.py +5 -0
  80. capytaine/ui/vtk/__init__.py +3 -0
  81. capytaine/ui/vtk/animation.py +329 -0
  82. capytaine/ui/vtk/body_viewer.py +28 -0
  83. capytaine/ui/vtk/helpers.py +82 -0
  84. capytaine/ui/vtk/mesh_viewer.py +461 -0
  85. capytaine-2.3.1.dist-info/LICENSE +674 -0
  86. capytaine-2.3.1.dist-info/METADATA +750 -0
  87. capytaine-2.3.1.dist-info/RECORD +93 -0
  88. capytaine-2.3.1.dist-info/WHEEL +6 -0
  89. capytaine-2.3.1.dist-info/entry_points.txt +3 -0
  90. capytaine.libs/libgfortran-83c28eba.so.5.0.0 +0 -0
  91. capytaine.libs/libgomp-e985bcbb.so.1.0.0 +0 -0
  92. capytaine.libs/libmvec-2-583a17db.28.so +0 -0
  93. capytaine.libs/libquadmath-2284e583.so.0.0.0 +0 -0
capytaine/__about__.py ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env python3
2
+
3
+ __all__ = ["__title__", "__description__", "__version__", "__author__", "__uri__", "__license__"]
4
+
5
+ __title__ = "capytaine"
6
+ __description__ = """Python BEM solver for linear potential flow, based on Nemoh"""
7
+
8
+ __version__ = "2.3.1"
9
+
10
+ __author__ = "Matthieu Ancellin"
11
+ __uri__ = "https://github.com/capytaine/capytaine"
12
+ __license__ = "GPL-3.0"
13
+
14
+
15
+ if __name__ == "__main__":
16
+ print(__version__)
capytaine/__init__.py ADDED
@@ -0,0 +1,36 @@
1
+ # Copyright (C) 2017-2019 Matthieu Ancellin
2
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
3
+
4
+ from .__about__ import (
5
+ __title__, __description__, __version__, __author__, __uri__, __license__
6
+ )
7
+
8
+ from capytaine.meshes.geometry import Axis, Plane, xOz_Plane, yOz_Plane, xOy_Plane
9
+ from capytaine.meshes.meshes import Mesh
10
+ from capytaine.meshes.collections import CollectionOfMeshes
11
+ from capytaine.meshes.symmetric import ReflectionSymmetricMesh, TranslationalSymmetricMesh, AxialSymmetricMesh
12
+ from capytaine.meshes.predefined.cylinders import mesh_disk, mesh_horizontal_cylinder, mesh_vertical_cylinder
13
+ from capytaine.meshes.predefined.spheres import mesh_sphere
14
+ from capytaine.meshes.predefined.rectangles import mesh_rectangle, mesh_parallelepiped
15
+
16
+ from capytaine.bodies.bodies import FloatingBody
17
+ from capytaine.bodies.dofs import rigid_body_dofs
18
+
19
+ from capytaine.bodies.predefined.spheres import Sphere
20
+ from capytaine.bodies.predefined.cylinders import VerticalCylinder, HorizontalCylinder, Disk
21
+ from capytaine.bodies.predefined.rectangles import Rectangle, RectangularParallelepiped, OpenRectangularParallelepiped
22
+
23
+ from capytaine.bem.problems_and_results import RadiationProblem, DiffractionProblem
24
+ from capytaine.bem.solver import BEMSolver
25
+ from capytaine.bem.engines import BasicMatrixEngine, HierarchicalToeplitzMatrixEngine, HierarchicalPrecondMatrixEngine
26
+ from capytaine.green_functions.delhommeau import Delhommeau, XieDelhommeau
27
+ from capytaine.green_functions.hams import LiangWuNoblesseGF, FinGreen3D, HAMS_GF
28
+
29
+ from capytaine.post_pro.free_surfaces import FreeSurface
30
+
31
+ from capytaine.io.mesh_loaders import load_mesh
32
+ from capytaine.io.xarray import assemble_dataframe, assemble_dataset, assemble_matrices, export_dataset
33
+
34
+ from capytaine.ui.rich import set_logging
35
+
36
+ set_logging(level="WARNING")
File without changes
@@ -0,0 +1,111 @@
1
+ """Computing the potential and velocity of Airy wave."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ import numpy as np
6
+ from capytaine.tools.lists_of_points import _normalize_points, _normalize_free_surface_points
7
+
8
+ def airy_waves_potential(points, pb):
9
+ """Compute the potential for Airy waves at a given point (or array of points).
10
+
11
+ Parameters
12
+ ----------
13
+ points: array of shape (3) or (N x 3)
14
+ coordinates of the points in which to evaluate the potential.
15
+ pb: DiffractionProblem
16
+ problem with the environmental conditions (g, rho, ...) of interest
17
+
18
+ Returns
19
+ -------
20
+ array of shape (1) or (N x 1)
21
+ The potential
22
+ """
23
+ points, output_shape = _normalize_points(points)
24
+
25
+ if float(pb.wavenumber) == 0.0 or float(pb.wavenumber) == np.inf:
26
+ return np.nan * np.ones(output_shape)
27
+
28
+ x, y, z = points.T
29
+ k = pb.wavenumber
30
+ h = pb.water_depth
31
+ beta = pb.encounter_wave_direction
32
+ wbar = x * np.cos(beta) + y * np.sin(beta)
33
+
34
+ if 0 <= k*h < 20:
35
+ cih = np.cosh(k*(z+h))/np.cosh(k*h)
36
+ # sih = np.sinh(k*(z+h))/np.cosh(k*h)
37
+ else:
38
+ cih = np.exp(k*z)
39
+ # sih = np.exp(k*z)
40
+
41
+ phi = -1j*pb.g/pb.omega * cih * np.exp(1j * k * wbar)
42
+ return phi.reshape(output_shape)
43
+
44
+
45
+ def airy_waves_velocity(points, pb):
46
+ """Compute the fluid velocity for Airy waves at a given point (or array of points).
47
+
48
+ Parameters
49
+ ----------
50
+ points: array of shape (3) or (N x 3)
51
+ coordinates of the points in which to evaluate the potential.
52
+ pb: DiffractionProblem
53
+ problem with the environmental conditions (g, rho, ...) of interest
54
+
55
+ Returns
56
+ -------
57
+ array of shape (3) or (N x 3)
58
+ the velocity vectors
59
+ """
60
+ points, output_shape = _normalize_points(points)
61
+
62
+ if float(pb.wavenumber) == 0.0 or float(pb.wavenumber) == np.inf:
63
+ return np.nan * np.ones((*output_shape, 3))
64
+
65
+ x, y, z = points.T
66
+ k = pb.wavenumber
67
+ h = pb.water_depth
68
+ beta = pb.encounter_wave_direction
69
+
70
+ wbar = x * np.cos(beta) + y * np.sin(beta)
71
+
72
+ if 0 <= k*h < 20:
73
+ cih = np.cosh(k*(z+h))/np.cosh(k*h)
74
+ sih = np.sinh(k*(z+h))/np.cosh(k*h)
75
+ else:
76
+ cih = np.exp(k*z)
77
+ sih = np.exp(k*z)
78
+
79
+ v = pb.g*k/pb.omega * \
80
+ np.exp(1j * k * wbar) * \
81
+ np.array([np.cos(pb.wave_direction) * cih, np.sin(pb.wave_direction) * cih, -1j * sih])
82
+
83
+ return v.T.reshape((*output_shape, 3))
84
+
85
+
86
+ def airy_waves_pressure(points, pb):
87
+ return 1j * float(pb.omega) * pb.rho * airy_waves_potential(points, pb)
88
+
89
+
90
+ def froude_krylov_force(pb):
91
+ return pb.body.integrate_pressure(airy_waves_pressure(pb.body.mesh.faces_centers, pb))
92
+
93
+
94
+ def airy_waves_free_surface_elevation(points, pb):
95
+ """Compute the free surface elevation at points of the undisturbed Airy waves
96
+
97
+ Parameters
98
+ ----------
99
+ points: array of shape (3) or (N × 3) or (2) or (N × 2)
100
+ coordinates of the points in which to evaluate the potential.
101
+ If only two coordinates are passed, the last one is filled with zeros.
102
+ pb: DiffractionProblem
103
+ problem with the environmental conditions (g, rho, ...) of interest
104
+
105
+ Returns
106
+ -------
107
+ complex-valued array of shape (1,) or (N,)
108
+ the free surface elevations
109
+ """
110
+ points, output_shape = _normalize_free_surface_points(points)
111
+ return 1j * pb.omega / pb.g * airy_waves_potential(points, pb).reshape(output_shape)
@@ -0,0 +1,441 @@
1
+ """Definition of the methods to build influence matrices, using possibly some sparse structures."""
2
+ # Copyright (C) 2017-2019 Matthieu Ancellin
3
+ # See LICENSE file at <https://github.com/mancellin/capytaine>
4
+
5
+ import logging
6
+ from abc import ABC, abstractmethod
7
+
8
+ import numpy as np
9
+ from scipy.linalg import lu_factor
10
+ from scipy.sparse import coo_matrix
11
+ from scipy.sparse import linalg as ssl
12
+
13
+ from capytaine.meshes.collections import CollectionOfMeshes
14
+ from capytaine.meshes.symmetric import ReflectionSymmetricMesh, TranslationalSymmetricMesh, AxialSymmetricMesh
15
+
16
+ from capytaine.matrices import linear_solvers
17
+ from capytaine.matrices.block import BlockMatrix
18
+ from capytaine.matrices.low_rank import LowRankMatrix, NoConvergenceOfACA
19
+ from capytaine.matrices.block_toeplitz import BlockSymmetricToeplitzMatrix, BlockToeplitzMatrix, BlockCirculantMatrix
20
+ from capytaine.tools.lru_cache import lru_cache_with_strict_maxsize
21
+
22
+ LOG = logging.getLogger(__name__)
23
+
24
+
25
+ ####################
26
+ # ABSTRACT CLASS #
27
+ ####################
28
+
29
+ class MatrixEngine(ABC):
30
+ """Abstract method to build a matrix."""
31
+
32
+ @abstractmethod
33
+ def build_matrices(self, mesh1, mesh2, free_surface, water_depth, wavenumber, green_function, adjoint_double_layer):
34
+ pass
35
+
36
+ def build_S_matrix(self, *args, **kwargs):
37
+ """Similar to :code:`build_matrices`, but returning only :math:`S`"""
38
+ S, _ = self.build_matrices(*args, **kwargs) # Could be optimized...
39
+ return S
40
+
41
+
42
+ ##################
43
+ # BASIC ENGINE #
44
+ ##################
45
+
46
+ class BasicMatrixEngine(MatrixEngine):
47
+ """
48
+ Simple engine that assemble a full matrix (except for one reflection symmetry).
49
+ Basically only calls :code:`green_function.evaluate`.
50
+
51
+ Parameters
52
+ ----------
53
+ linear_solver: str or function, optional
54
+ Setting of the numerical solver for linear problems Ax = b.
55
+ It can be set with the name of a preexisting solver
56
+ (available: "direct" and "gmres", the former is the default choice)
57
+ or by passing directly a solver function.
58
+ matrix_cache_size: int, optional
59
+ number of matrices to keep in cache
60
+ """
61
+
62
+ available_linear_solvers = {'direct': linear_solvers.solve_directly,
63
+ 'lu_decomposition': linear_solvers.LUSolverWithCache().solve,
64
+ 'gmres': linear_solvers.solve_gmres,
65
+ }
66
+
67
+ def __init__(self, *, linear_solver='lu_decomposition', matrix_cache_size=1):
68
+
69
+ if linear_solver in self.available_linear_solvers:
70
+ self.linear_solver = self.available_linear_solvers[linear_solver]
71
+ else:
72
+ self.linear_solver = linear_solver
73
+
74
+ if matrix_cache_size > 0:
75
+ self.build_matrices = lru_cache_with_strict_maxsize(maxsize=matrix_cache_size)(self.build_matrices)
76
+
77
+ self.exportable_settings = {
78
+ 'engine': 'BasicMatrixEngine',
79
+ 'matrix_cache_size': matrix_cache_size,
80
+ 'linear_solver': str(linear_solver),
81
+ }
82
+
83
+ def __str__(self):
84
+ params = f"linear_solver=\'{self.exportable_settings['linear_solver']}\'"
85
+ params += f", matrix_cache_size={self.exportable_settings['matrix_cache_size']}" if self.exportable_settings['matrix_cache_size'] != 1 else ""
86
+ return f"BasicMatrixEngine({params})"
87
+
88
+ def __repr__(self):
89
+ return self.__str__()
90
+
91
+ def _repr_pretty_(self, p, cycle):
92
+ p.text(self.__str__())
93
+
94
+ def build_matrices(self, mesh1, mesh2, free_surface, water_depth, wavenumber, green_function, adjoint_double_layer=True):
95
+ r"""Build the influence matrices between mesh1 and mesh2.
96
+
97
+ Parameters
98
+ ----------
99
+ mesh1: Mesh or CollectionOfMeshes
100
+ mesh of the receiving body (where the potential is measured)
101
+ mesh2: Mesh or CollectionOfMeshes
102
+ mesh of the source body (over which the source distribution is integrated)
103
+ free_surface: float
104
+ position of the free surface (default: :math:`z = 0`)
105
+ water_depth: float
106
+ position of the sea bottom (default: :math:`z = -\infty`)
107
+ wavenumber: float
108
+ wavenumber (default: 1.0)
109
+ green_function: AbstractGreenFunction
110
+ object with an "evaluate" method that computes the Green function.
111
+ adjoint_double_layer: bool, optional
112
+ compute double layer for direct method (F) or adjoint double layer for indirect method (T) matrices (default: True)
113
+
114
+ Returns
115
+ -------
116
+ tuple of matrix-like
117
+ the matrices :math:`S` and :math:`K`
118
+ """
119
+
120
+ if (isinstance(mesh1, ReflectionSymmetricMesh)
121
+ and isinstance(mesh2, ReflectionSymmetricMesh)
122
+ and mesh1.plane == mesh2.plane):
123
+
124
+ S_a, V_a = self.build_matrices(
125
+ mesh1[0], mesh2[0], free_surface, water_depth, wavenumber,
126
+ green_function, adjoint_double_layer=adjoint_double_layer)
127
+ S_b, V_b = self.build_matrices(
128
+ mesh1[0], mesh2[1], free_surface, water_depth, wavenumber,
129
+ green_function, adjoint_double_layer=adjoint_double_layer)
130
+
131
+ return BlockSymmetricToeplitzMatrix([[S_a, S_b]]), BlockSymmetricToeplitzMatrix([[V_a, V_b]])
132
+
133
+ else:
134
+ return green_function.evaluate(
135
+ mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer=adjoint_double_layer
136
+ )
137
+
138
+ ###################################
139
+ # HIERARCHIAL TOEPLITZ MATRICES #
140
+ ###################################
141
+
142
+ class HierarchicalToeplitzMatrixEngine(MatrixEngine):
143
+ """An experimental matrix engine that build a hierarchical matrix with
144
+ some block-Toeplitz structure.
145
+
146
+ Parameters
147
+ ----------
148
+ ACA_distance: float, optional
149
+ Above this distance, the ACA is used to approximate the matrix with a low-rank block.
150
+ ACA_tol: float, optional
151
+ The tolerance of the ACA when building a low-rank matrix.
152
+ matrix_cache_size: int, optional
153
+ number of matrices to keep in cache
154
+ """
155
+
156
+ def __init__(self, *, ACA_distance=8.0, ACA_tol=1e-2, matrix_cache_size=1):
157
+
158
+ if matrix_cache_size > 0:
159
+ self.build_matrices = lru_cache_with_strict_maxsize(maxsize=matrix_cache_size)(self.build_matrices)
160
+
161
+ self.ACA_distance = ACA_distance
162
+ self.ACA_tol = ACA_tol
163
+
164
+ self.linear_solver = linear_solvers.solve_gmres
165
+
166
+ self.exportable_settings = {
167
+ 'engine': 'HierarchicalToeplitzMatrixEngine',
168
+ 'ACA_distance': ACA_distance,
169
+ 'ACA_tol': ACA_tol,
170
+ 'matrix_cache_size': matrix_cache_size,
171
+ }
172
+
173
+ def __str__(self):
174
+ params = f"ACA_distance={self.ACA_distance}"
175
+ params += f", ACA_tol={self.ACA_tol}"
176
+ params += f", matrix_cache_size={self.exportable_settings['matrix_cache_size']}" if self.exportable_settings['matrix_cache_size'] != 1 else ""
177
+ return f"HierarchicalToeplitzMatrixEngine({params})"
178
+
179
+ def _repr_pretty_(self, p, cycle):
180
+ p.text(self.__str__())
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
+
327
+ else:
328
+ LOG.debug(log_entry)
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
334
+
335
+ class HierarchicalPrecondMatrixEngine(HierarchicalToeplitzMatrixEngine):
336
+ """An experimental matrix engine that build a hierarchical matrix with
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)