capytaine 3.0.0a1__cp314-cp314t-macosx_15_0_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 (65) hide show
  1. capytaine/.dylibs/libgcc_s.1.1.dylib +0 -0
  2. capytaine/.dylibs/libgfortran.5.dylib +0 -0
  3. capytaine/.dylibs/libquadmath.0.dylib +0 -0
  4. capytaine/__about__.py +21 -0
  5. capytaine/__init__.py +32 -0
  6. capytaine/bem/__init__.py +0 -0
  7. capytaine/bem/airy_waves.py +111 -0
  8. capytaine/bem/engines.py +321 -0
  9. capytaine/bem/problems_and_results.py +601 -0
  10. capytaine/bem/solver.py +718 -0
  11. capytaine/bodies/__init__.py +4 -0
  12. capytaine/bodies/bodies.py +630 -0
  13. capytaine/bodies/dofs.py +146 -0
  14. capytaine/bodies/hydrostatics.py +540 -0
  15. capytaine/bodies/multibodies.py +216 -0
  16. capytaine/green_functions/Delhommeau_float32.cpython-314t-darwin.so +0 -0
  17. capytaine/green_functions/Delhommeau_float64.cpython-314t-darwin.so +0 -0
  18. capytaine/green_functions/__init__.py +2 -0
  19. capytaine/green_functions/abstract_green_function.py +64 -0
  20. capytaine/green_functions/delhommeau.py +522 -0
  21. capytaine/green_functions/hams.py +210 -0
  22. capytaine/io/__init__.py +0 -0
  23. capytaine/io/bemio.py +153 -0
  24. capytaine/io/legacy.py +228 -0
  25. capytaine/io/wamit.py +479 -0
  26. capytaine/io/xarray.py +673 -0
  27. capytaine/meshes/__init__.py +2 -0
  28. capytaine/meshes/abstract_meshes.py +375 -0
  29. capytaine/meshes/clean.py +302 -0
  30. capytaine/meshes/clip.py +347 -0
  31. capytaine/meshes/export.py +89 -0
  32. capytaine/meshes/geometry.py +259 -0
  33. capytaine/meshes/io.py +433 -0
  34. capytaine/meshes/meshes.py +826 -0
  35. capytaine/meshes/predefined/__init__.py +6 -0
  36. capytaine/meshes/predefined/cylinders.py +280 -0
  37. capytaine/meshes/predefined/rectangles.py +202 -0
  38. capytaine/meshes/predefined/spheres.py +55 -0
  39. capytaine/meshes/quality.py +159 -0
  40. capytaine/meshes/surface_integrals.py +82 -0
  41. capytaine/meshes/symmetric_meshes.py +641 -0
  42. capytaine/meshes/visualization.py +353 -0
  43. capytaine/post_pro/__init__.py +6 -0
  44. capytaine/post_pro/free_surfaces.py +85 -0
  45. capytaine/post_pro/impedance.py +92 -0
  46. capytaine/post_pro/kochin.py +54 -0
  47. capytaine/post_pro/rao.py +60 -0
  48. capytaine/tools/__init__.py +0 -0
  49. capytaine/tools/block_circulant_matrices.py +275 -0
  50. capytaine/tools/cache_on_disk.py +26 -0
  51. capytaine/tools/deprecation_handling.py +18 -0
  52. capytaine/tools/lists_of_points.py +52 -0
  53. capytaine/tools/memory_monitor.py +45 -0
  54. capytaine/tools/optional_imports.py +27 -0
  55. capytaine/tools/prony_decomposition.py +150 -0
  56. capytaine/tools/symbolic_multiplication.py +161 -0
  57. capytaine/tools/timer.py +90 -0
  58. capytaine/ui/__init__.py +0 -0
  59. capytaine/ui/cli.py +28 -0
  60. capytaine/ui/rich.py +5 -0
  61. capytaine-3.0.0a1.dist-info/LICENSE +674 -0
  62. capytaine-3.0.0a1.dist-info/METADATA +755 -0
  63. capytaine-3.0.0a1.dist-info/RECORD +65 -0
  64. capytaine-3.0.0a1.dist-info/WHEEL +6 -0
  65. capytaine-3.0.0a1.dist-info/entry_points.txt +3 -0
Binary file
Binary file
Binary file
capytaine/__about__.py ADDED
@@ -0,0 +1,21 @@
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
+ __author__ = "Matthieu Ancellin"
9
+ __uri__ = "https://github.com/capytaine/capytaine"
10
+ __license__ = "GPL-3.0"
11
+
12
+ __version__ = "3.0.0a1"
13
+
14
+ __build_info__ = {
15
+ "compiler": "gcc 13.4.0",
16
+ "build_time": "2026-02-02T13:56:49Z"
17
+ }
18
+
19
+
20
+ if __name__ == "__main__":
21
+ print(__version__)
capytaine/__init__.py ADDED
@@ -0,0 +1,32 @@
1
+ # Copyright (C) 2017-2025 Matthieu Ancellin
2
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
3
+
4
+ from .__about__ import (
5
+ __title__, __description__, __version__, __author__, __uri__, __license__, __build_info__
6
+ )
7
+
8
+ from capytaine.meshes.meshes import Mesh
9
+ from capytaine.meshes.io import load_mesh
10
+ from capytaine.meshes.symmetric_meshes import ReflectionSymmetricMesh, RotationSymmetricMesh
11
+
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.multibodies import Multibody
18
+ from capytaine.bodies.dofs import rigid_body_dofs
19
+
20
+ from capytaine.bem.problems_and_results import RadiationProblem, DiffractionProblem
21
+ from capytaine.bem.solver import BEMSolver
22
+ from capytaine.bem.engines import BasicMatrixEngine
23
+ from capytaine.green_functions.delhommeau import Delhommeau, XieDelhommeau
24
+ from capytaine.green_functions.hams import LiangWuNoblesseGF, FinGreen3D, HAMS_GF
25
+
26
+ from capytaine.post_pro.free_surfaces import FreeSurface
27
+
28
+ from capytaine.io.xarray import assemble_dataframe, assemble_dataset, assemble_matrices, export_dataset
29
+
30
+ from capytaine.ui.rich import set_logging
31
+
32
+ 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,321 @@
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/capytaine/capytaine>
4
+
5
+ import logging
6
+ from abc import ABC, abstractmethod
7
+ from typing import Tuple, Union, Optional, Callable
8
+
9
+ import numpy as np
10
+ import scipy.sparse.linalg as ssl
11
+
12
+ from capytaine.meshes.symmetric_meshes import ReflectionSymmetricMesh, RotationSymmetricMesh
13
+
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
+
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, adjoint_double_layer):
34
+ pass
35
+
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
43
+
44
+
45
+ ##################
46
+ # BASIC ENGINE #
47
+ ##################
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
+
78
+ class BasicMatrixEngine(MatrixEngine):
79
+ """
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.
86
+
87
+ Parameters
88
+ ----------
89
+ green_function: AbstractGreenFunction
90
+ the low level implementation used to compute the coefficients of the matrices.
91
+ linear_solver: str or function, optional
92
+ Setting of the numerical solver for linear problems Ax = b.
93
+ It can be set with the name of a preexisting solver
94
+ (available: "lu_decomposition", "lu_decompositon_with_overwrite" and "gmres", the former is the default choice)
95
+ or by passing directly a solver function.
96
+ """
97
+
98
+ green_function: AbstractGreenFunction
99
+ _linear_solver: Union[str, Callable]
100
+ last_computed_matrices: Optional[Tuple[MatrixLike, LUDecomposedMatrixOrNot]]
101
+
102
+ def __init__(self, *, green_function=None, linear_solver='lu_decomposition'):
103
+
104
+ self.green_function = Delhommeau() if green_function is None else green_function
105
+
106
+ self._linear_solver = linear_solver
107
+
108
+ self.last_computed_inputs = None
109
+ self.last_computed_matrices = None
110
+
111
+ self.exportable_settings = {
112
+ 'engine': 'BasicMatrixEngine',
113
+ 'linear_solver': str(linear_solver),
114
+ **self.green_function.exportable_settings,
115
+ }
116
+
117
+ def __str__(self):
118
+ params= [f"green_function={self.green_function}", f"linear_solver={repr(self._linear_solver)}"]
119
+ return f"BasicMatrixEngine({', '.join(params)})"
120
+
121
+ def __repr__(self):
122
+ return self.__str__()
123
+
124
+ def _repr_pretty_(self, p, cycle):
125
+ p.text(self.__str__())
126
+
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):
197
+ r"""Build the influence matrices between mesh1 and mesh2.
198
+
199
+ Parameters
200
+ ----------
201
+ mesh1: MeshLike or list of points
202
+ mesh of the receiving body (where the potential is measured)
203
+ mesh2: MeshLike
204
+ mesh of the source body (over which the source distribution is integrated)
205
+ free_surface: float
206
+ position of the free surface (default: :math:`z = 0`)
207
+ water_depth: float
208
+ position of the sea bottom (default: :math:`z = -\infty`)
209
+ wavenumber: float
210
+ wavenumber (default: 1.0)
211
+ adjoint_double_layer: bool, optional
212
+ compute double layer for direct method (F) or adjoint double layer for indirect method (T) matrices (default: True)
213
+
214
+ Returns
215
+ -------
216
+ tuple of matrix-like (Numpy arrays or BlockCirculantMatrix)
217
+ the matrices :math:`S` and :math:`K`
218
+ """
219
+ return self._build_and_cache_matrices_with_symmetries(
220
+ mesh1, mesh2, **gf_params
221
+ )
222
+
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
225
+
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
232
+
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)
265
+
266
+ else:
267
+ raise NotImplementedError(
268
+ f"Unknown `linear_solver` in BasicMatrixEngine: {self._linear_solver}"
269
+ )
270
+
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
+ },
305
+ }
306
+
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
317
+ else:
318
+ symmetry_factor = 1.0
319
+
320
+ memory_peak = symmetry_factor * nb_faces**2 * nb_matrices * nb_bytes/1e9
321
+ return memory_peak