capytaine 2.3.1__cp314-cp314t-macosx_14_0_arm64.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/.dylibs/libgcc_s.1.1.dylib +0 -0
- capytaine/.dylibs/libgfortran.5.dylib +0 -0
- capytaine/.dylibs/libquadmath.0.dylib +0 -0
- capytaine/__about__.py +16 -0
- capytaine/__init__.py +36 -0
- capytaine/bem/__init__.py +0 -0
- capytaine/bem/airy_waves.py +111 -0
- capytaine/bem/engines.py +441 -0
- capytaine/bem/problems_and_results.py +600 -0
- capytaine/bem/solver.py +594 -0
- capytaine/bodies/__init__.py +4 -0
- capytaine/bodies/bodies.py +1221 -0
- capytaine/bodies/dofs.py +19 -0
- capytaine/bodies/predefined/__init__.py +6 -0
- capytaine/bodies/predefined/cylinders.py +151 -0
- capytaine/bodies/predefined/rectangles.py +111 -0
- capytaine/bodies/predefined/spheres.py +70 -0
- capytaine/green_functions/FinGreen3D/.gitignore +1 -0
- capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +3589 -0
- capytaine/green_functions/FinGreen3D/LICENSE +165 -0
- capytaine/green_functions/FinGreen3D/Makefile +16 -0
- capytaine/green_functions/FinGreen3D/README.md +24 -0
- capytaine/green_functions/FinGreen3D/test_program.f90 +39 -0
- capytaine/green_functions/LiangWuNoblesse/.gitignore +1 -0
- capytaine/green_functions/LiangWuNoblesse/LICENSE +504 -0
- capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +751 -0
- capytaine/green_functions/LiangWuNoblesse/Makefile +16 -0
- capytaine/green_functions/LiangWuNoblesse/README.md +2 -0
- capytaine/green_functions/LiangWuNoblesse/test_program.f90 +28 -0
- capytaine/green_functions/__init__.py +2 -0
- capytaine/green_functions/abstract_green_function.py +64 -0
- capytaine/green_functions/delhommeau.py +507 -0
- capytaine/green_functions/hams.py +204 -0
- capytaine/green_functions/libs/Delhommeau_float32.cpython-314t-darwin.so +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cpython-314t-darwin.so +0 -0
- capytaine/green_functions/libs/__init__.py +0 -0
- capytaine/io/__init__.py +0 -0
- capytaine/io/bemio.py +153 -0
- capytaine/io/legacy.py +328 -0
- capytaine/io/mesh_loaders.py +1086 -0
- capytaine/io/mesh_writers.py +692 -0
- capytaine/io/meshio.py +38 -0
- capytaine/io/wamit.py +479 -0
- capytaine/io/xarray.py +668 -0
- capytaine/matrices/__init__.py +16 -0
- capytaine/matrices/block.py +592 -0
- capytaine/matrices/block_toeplitz.py +325 -0
- capytaine/matrices/builders.py +89 -0
- capytaine/matrices/linear_solvers.py +232 -0
- capytaine/matrices/low_rank.py +395 -0
- capytaine/meshes/__init__.py +6 -0
- capytaine/meshes/clipper.py +465 -0
- capytaine/meshes/collections.py +342 -0
- capytaine/meshes/geometry.py +409 -0
- capytaine/meshes/mesh_like_protocol.py +37 -0
- capytaine/meshes/meshes.py +890 -0
- capytaine/meshes/predefined/__init__.py +6 -0
- capytaine/meshes/predefined/cylinders.py +314 -0
- capytaine/meshes/predefined/rectangles.py +261 -0
- capytaine/meshes/predefined/spheres.py +62 -0
- capytaine/meshes/properties.py +276 -0
- capytaine/meshes/quadratures.py +80 -0
- capytaine/meshes/quality.py +448 -0
- capytaine/meshes/surface_integrals.py +63 -0
- capytaine/meshes/symmetric.py +462 -0
- capytaine/post_pro/__init__.py +6 -0
- capytaine/post_pro/free_surfaces.py +88 -0
- capytaine/post_pro/impedance.py +92 -0
- capytaine/post_pro/kochin.py +54 -0
- capytaine/post_pro/rao.py +60 -0
- capytaine/tools/__init__.py +0 -0
- capytaine/tools/cache_on_disk.py +26 -0
- capytaine/tools/deprecation_handling.py +18 -0
- capytaine/tools/lists_of_points.py +52 -0
- capytaine/tools/lru_cache.py +49 -0
- capytaine/tools/optional_imports.py +27 -0
- capytaine/tools/prony_decomposition.py +150 -0
- capytaine/tools/symbolic_multiplication.py +149 -0
- capytaine/tools/timer.py +66 -0
- capytaine/ui/__init__.py +0 -0
- capytaine/ui/cli.py +28 -0
- capytaine/ui/rich.py +5 -0
- capytaine/ui/vtk/__init__.py +3 -0
- capytaine/ui/vtk/animation.py +329 -0
- capytaine/ui/vtk/body_viewer.py +28 -0
- capytaine/ui/vtk/helpers.py +82 -0
- capytaine/ui/vtk/mesh_viewer.py +461 -0
- capytaine-2.3.1.dist-info/LICENSE +674 -0
- capytaine-2.3.1.dist-info/METADATA +750 -0
- capytaine-2.3.1.dist-info/RECORD +92 -0
- capytaine-2.3.1.dist-info/WHEEL +6 -0
- capytaine-2.3.1.dist-info/entry_points.txt +3 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
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)
|
capytaine/bem/engines.py
ADDED
|
@@ -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)
|