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.
- 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 +21 -0
- capytaine/__init__.py +32 -0
- capytaine/bem/__init__.py +0 -0
- capytaine/bem/airy_waves.py +111 -0
- capytaine/bem/engines.py +321 -0
- capytaine/bem/problems_and_results.py +601 -0
- capytaine/bem/solver.py +718 -0
- capytaine/bodies/__init__.py +4 -0
- capytaine/bodies/bodies.py +630 -0
- capytaine/bodies/dofs.py +146 -0
- capytaine/bodies/hydrostatics.py +540 -0
- capytaine/bodies/multibodies.py +216 -0
- capytaine/green_functions/Delhommeau_float32.cpython-314t-darwin.so +0 -0
- capytaine/green_functions/Delhommeau_float64.cpython-314t-darwin.so +0 -0
- capytaine/green_functions/__init__.py +2 -0
- capytaine/green_functions/abstract_green_function.py +64 -0
- capytaine/green_functions/delhommeau.py +522 -0
- capytaine/green_functions/hams.py +210 -0
- capytaine/io/__init__.py +0 -0
- capytaine/io/bemio.py +153 -0
- capytaine/io/legacy.py +228 -0
- capytaine/io/wamit.py +479 -0
- capytaine/io/xarray.py +673 -0
- capytaine/meshes/__init__.py +2 -0
- 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 +259 -0
- capytaine/meshes/io.py +433 -0
- capytaine/meshes/meshes.py +826 -0
- capytaine/meshes/predefined/__init__.py +6 -0
- capytaine/meshes/predefined/cylinders.py +280 -0
- capytaine/meshes/predefined/rectangles.py +202 -0
- capytaine/meshes/predefined/spheres.py +55 -0
- capytaine/meshes/quality.py +159 -0
- capytaine/meshes/surface_integrals.py +82 -0
- capytaine/meshes/symmetric_meshes.py +641 -0
- capytaine/meshes/visualization.py +353 -0
- capytaine/post_pro/__init__.py +6 -0
- capytaine/post_pro/free_surfaces.py +85 -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/block_circulant_matrices.py +275 -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/memory_monitor.py +45 -0
- capytaine/tools/optional_imports.py +27 -0
- capytaine/tools/prony_decomposition.py +150 -0
- capytaine/tools/symbolic_multiplication.py +161 -0
- capytaine/tools/timer.py +90 -0
- capytaine/ui/__init__.py +0 -0
- capytaine/ui/cli.py +28 -0
- capytaine/ui/rich.py +5 -0
- capytaine-3.0.0a1.dist-info/LICENSE +674 -0
- capytaine-3.0.0a1.dist-info/METADATA +755 -0
- capytaine-3.0.0a1.dist-info/RECORD +65 -0
- capytaine-3.0.0a1.dist-info/WHEEL +6 -0
- capytaine-3.0.0a1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from itertools import chain, accumulate
|
|
5
|
+
from typing import Union, List, Optional
|
|
6
|
+
from functools import cached_property, lru_cache
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import xarray as xr
|
|
10
|
+
|
|
11
|
+
from capytaine.bodies.dofs import (
|
|
12
|
+
AbstractDof,
|
|
13
|
+
DofOnSubmesh,
|
|
14
|
+
add_dofs_labels_to_vector,
|
|
15
|
+
add_dofs_labels_to_matrix
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
LOG = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Multibody:
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
bodies: List[Union[FloatingBody, Multibody]],
|
|
25
|
+
# own_dofs: Optional[Dict[str, np.array]] = None,
|
|
26
|
+
*,
|
|
27
|
+
name: Optional[str] = None
|
|
28
|
+
):
|
|
29
|
+
self.bodies = bodies
|
|
30
|
+
|
|
31
|
+
if len(set(b.name for b in self.bodies)) < len(self.bodies):
|
|
32
|
+
raise ValueError(
|
|
33
|
+
"In Multibody, all component bodies must have a distinct name.\n"
|
|
34
|
+
f"Got: {[b.name for b in self.bodies]}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# if own_dofs is None:
|
|
38
|
+
# self.own_dofs = {}
|
|
39
|
+
# else:
|
|
40
|
+
# self.own_dofs = own_dofs
|
|
41
|
+
|
|
42
|
+
if name is None:
|
|
43
|
+
self.name = '+'.join(b.name for b in self.bodies)
|
|
44
|
+
else:
|
|
45
|
+
self.name = name
|
|
46
|
+
|
|
47
|
+
# Keep legacy behavior of former mesh joining
|
|
48
|
+
for matrix_name in ["inertia_matrix", "hydrostatic_stiffness"]:
|
|
49
|
+
if all(hasattr(body, matrix_name) for body in bodies):
|
|
50
|
+
from scipy.linalg import block_diag
|
|
51
|
+
setattr(self, matrix_name, self.add_dofs_labels_to_matrix(
|
|
52
|
+
block_diag(*[getattr(body, matrix_name) for body in bodies])
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
LOG.debug(f"New multibody: {self.__str__()}.")
|
|
56
|
+
|
|
57
|
+
@lru_cache
|
|
58
|
+
def as_FloatingBody(self):
|
|
59
|
+
from capytaine.bodies.bodies import FloatingBody
|
|
60
|
+
if all(body.mass is not None for body in self.bodies):
|
|
61
|
+
total_mass = sum(body.mass for body in self.bodies)
|
|
62
|
+
else:
|
|
63
|
+
total_mass = None
|
|
64
|
+
|
|
65
|
+
if (all(body.mass is not None for body in self.bodies)
|
|
66
|
+
and all(body.center_of_mass is not None for body in self.bodies)):
|
|
67
|
+
new_cog = sum(body.mass*np.asarray(body.center_of_mass) for body in self.bodies)/total_mass
|
|
68
|
+
else:
|
|
69
|
+
new_cog = None
|
|
70
|
+
|
|
71
|
+
return FloatingBody(
|
|
72
|
+
mesh=self.mesh,
|
|
73
|
+
dofs=self.dofs,
|
|
74
|
+
lid_mesh=self.lid_mesh,
|
|
75
|
+
mass=total_mass,
|
|
76
|
+
center_of_mass=new_cog,
|
|
77
|
+
name=self.name,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def __str__(self):
|
|
81
|
+
short_bodies = ', '.join(b.__short_str__() for b in self.bodies)
|
|
82
|
+
# short_dofs = '{' + ', '.join('"{}": ...'.format(d) for d in self.own_dofs) + '}'
|
|
83
|
+
return f"Multibody({short_bodies})" #, own_dofs={short_dofs})"
|
|
84
|
+
|
|
85
|
+
def __short_str__(self):
|
|
86
|
+
return str(self)
|
|
87
|
+
|
|
88
|
+
def __repr__(self):
|
|
89
|
+
return str(self)
|
|
90
|
+
|
|
91
|
+
def _check_dofs_shape_consistency(self):
|
|
92
|
+
# TODO
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
@cached_property
|
|
96
|
+
def minimal_computable_wavelength(self):
|
|
97
|
+
return min(b.minimal_computable_wavelength for b in self.bodies)
|
|
98
|
+
|
|
99
|
+
def first_irregular_frequency_estimate(self, *args, **kwargs):
|
|
100
|
+
return min(b.first_irregular_frequency_estimate(*args, **kwargs) for b in self.bodies)
|
|
101
|
+
|
|
102
|
+
@cached_property
|
|
103
|
+
def mesh(self):
|
|
104
|
+
return self.bodies[0].mesh.join_meshes(*[b.mesh for b in self.bodies[1:]])
|
|
105
|
+
|
|
106
|
+
@cached_property
|
|
107
|
+
def lid_mesh(self):
|
|
108
|
+
if all(body.lid_mesh is None for body in self.bodies):
|
|
109
|
+
return None
|
|
110
|
+
else:
|
|
111
|
+
lid_meshes = [body.lid_mesh.copy() for body in self.bodies if body.lid_mesh is not None]
|
|
112
|
+
joined_lid = lid_meshes[0].join_meshes(*lid_meshes[1:], name=f"{self.name}_lid_mesh")
|
|
113
|
+
return joined_lid
|
|
114
|
+
|
|
115
|
+
@cached_property
|
|
116
|
+
def mesh_including_lid(self):
|
|
117
|
+
return self.bodies[0].mesh_including_lid.join_meshes(*[b.mesh_including_lid for b in self.bodies[1:]])
|
|
118
|
+
|
|
119
|
+
@cached_property
|
|
120
|
+
def hull_mask(self):
|
|
121
|
+
return np.concatenate([b.hull_mask for b in self.bodies])
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def nb_dofs(self):
|
|
125
|
+
return sum(b.nb_dofs for b in self.bodies) # + len(self.own_dofs)
|
|
126
|
+
|
|
127
|
+
@cached_property
|
|
128
|
+
def dofs(self):
|
|
129
|
+
for body in self.bodies:
|
|
130
|
+
body._check_dofs_shape_consistency()
|
|
131
|
+
|
|
132
|
+
componenents_dofs = {}
|
|
133
|
+
cum_nb_faces = accumulate(chain([0], (body.mesh.nb_faces for body in self.bodies)))
|
|
134
|
+
total_nb_faces = sum(body.mesh.nb_faces for body in self.bodies)
|
|
135
|
+
for body, nbf in zip(self.bodies, cum_nb_faces):
|
|
136
|
+
# nbf is the cumulative number of faces of the previous subbodies,
|
|
137
|
+
# that is the offset of the indices of the faces of the current body.
|
|
138
|
+
for name, dof in body.dofs.items():
|
|
139
|
+
if isinstance(dof, AbstractDof):
|
|
140
|
+
new_dof = DofOnSubmesh(dof, range(nbf, nbf+body.mesh.nb_faces))
|
|
141
|
+
else:
|
|
142
|
+
new_dof = np.zeros((total_nb_faces, 3))
|
|
143
|
+
new_dof[nbf:nbf+len(dof), :] = dof
|
|
144
|
+
|
|
145
|
+
if '__' not in name:
|
|
146
|
+
new_dof_name = '__'.join([body.name, name])
|
|
147
|
+
else:
|
|
148
|
+
# The body is probably a combination of bodies already.
|
|
149
|
+
# So for the associativity of the + operation,
|
|
150
|
+
# it is better to keep the same name.
|
|
151
|
+
new_dof_name = name
|
|
152
|
+
componenents_dofs[new_dof_name] = new_dof
|
|
153
|
+
|
|
154
|
+
return {**componenents_dofs} #, **self.own_dofs}
|
|
155
|
+
|
|
156
|
+
def add_dofs_labels_to_vector(self, vector):
|
|
157
|
+
"""Helper function turning a bare vector into a vector labelled by the name of the dofs of the body,
|
|
158
|
+
to be used for instance for the computation of RAO."""
|
|
159
|
+
return add_dofs_labels_to_vector(self.dofs.keys(), vector)
|
|
160
|
+
|
|
161
|
+
def add_dofs_labels_to_matrix(self, matrix):
|
|
162
|
+
"""Helper function turning a bare matrix into a matrix labelled by the name of the dofs of the body,
|
|
163
|
+
to be used for instance for the computation of RAO."""
|
|
164
|
+
return add_dofs_labels_to_matrix(self.dofs.keys(), matrix)
|
|
165
|
+
|
|
166
|
+
def immersed_part(self, *args, **kwargs):
|
|
167
|
+
return Multibody(
|
|
168
|
+
[b.immersed_part() for b in self.bodies],
|
|
169
|
+
# own_dofs=None, # TODO
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def __add__(self, body_to_add):
|
|
173
|
+
return self.join_bodies(body_to_add)
|
|
174
|
+
|
|
175
|
+
def join_bodies(*bodies, name=None):
|
|
176
|
+
from capytaine.bodies.multibodies import Multibody
|
|
177
|
+
return Multibody(bodies, name=name)
|
|
178
|
+
|
|
179
|
+
def integrate_pressure(self, pressure):
|
|
180
|
+
return self.as_FloatingBody().integrate_pressure(pressure)
|
|
181
|
+
|
|
182
|
+
@cached_property
|
|
183
|
+
def center_of_buoyancy(self):
|
|
184
|
+
return {b.name: b.center_of_buoyancy for b in self.bodies}
|
|
185
|
+
|
|
186
|
+
@cached_property
|
|
187
|
+
def center_of_mass(self):
|
|
188
|
+
return {b.name: b.center_of_mass for b in self.bodies}
|
|
189
|
+
|
|
190
|
+
@cached_property
|
|
191
|
+
def volume(self):
|
|
192
|
+
return {b.name: b.volume for b in self.bodies}
|
|
193
|
+
|
|
194
|
+
@cached_property
|
|
195
|
+
def mass(self):
|
|
196
|
+
return {b.name: b.mass for b in self.bodies}
|
|
197
|
+
|
|
198
|
+
def _combine_component_matrices(self, matrices):
|
|
199
|
+
for m, b in zip(matrices, self.bodies):
|
|
200
|
+
m.coords['radiating_dof'] = np.array([b.name + '__' + k for k in m.coords['radiating_dof'].values])
|
|
201
|
+
m.coords['influenced_dof'] = np.array([b.name + '__' + k for k in m.coords['influenced_dof'].values])
|
|
202
|
+
|
|
203
|
+
return xr.concat(
|
|
204
|
+
matrices,
|
|
205
|
+
dim="radiating_dof",
|
|
206
|
+
fill_value=0.0
|
|
207
|
+
).sel(
|
|
208
|
+
radiating_dof=list(self.dofs.keys()),
|
|
209
|
+
influenced_dof=list(self.dofs.keys())
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def compute_hydrostatic_stiffness(self, *, rho=1000.0, g=9.81):
|
|
213
|
+
return self._combine_component_matrices([b.compute_hydrostatic_stiffness(rho=rho, g=g) for b in self.bodies])
|
|
214
|
+
|
|
215
|
+
def compute_rigid_body_inertia(self, rho=1000.0):
|
|
216
|
+
return self._combine_component_matrices([b.compute_rigid_body_inertia(rho=rho) for b in self.bodies])
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Abstract structure of a class used to compute the Green function"""
|
|
2
|
+
# Copyright (C) 2017-2024 Matthieu Ancellin
|
|
3
|
+
# See LICENSE file at <https://github.com/capytaine/capytaine>
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from capytaine.meshes.abstract_meshes import AbstractMesh
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GreenFunctionEvaluationError(Exception):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AbstractGreenFunction(ABC):
|
|
17
|
+
"""Abstract method to evaluate the Green function."""
|
|
18
|
+
|
|
19
|
+
floating_point_precision: str
|
|
20
|
+
|
|
21
|
+
def _get_colocation_points_and_normals(self, mesh1, mesh2, adjoint_double_layer):
|
|
22
|
+
if isinstance(mesh1, AbstractMesh):
|
|
23
|
+
collocation_points = mesh1.faces_centers
|
|
24
|
+
nb_collocation_points = mesh1.nb_faces
|
|
25
|
+
if not adjoint_double_layer: # Computing the D matrix
|
|
26
|
+
early_dot_product_normals = mesh2.faces_normals
|
|
27
|
+
else: # Computing the K matrix
|
|
28
|
+
early_dot_product_normals = mesh1.faces_normals
|
|
29
|
+
|
|
30
|
+
elif isinstance(mesh1, np.ndarray) and mesh1.ndim == 2 and mesh1.shape[1] == 3:
|
|
31
|
+
# This is used when computing potential or velocity at given points in postprocessing
|
|
32
|
+
collocation_points = mesh1
|
|
33
|
+
nb_collocation_points = mesh1.shape[0]
|
|
34
|
+
if not adjoint_double_layer: # Computing the D matrix
|
|
35
|
+
early_dot_product_normals = mesh2.faces_normals
|
|
36
|
+
else: # Computing the K matrix
|
|
37
|
+
early_dot_product_normals = np.zeros((nb_collocation_points, 3))
|
|
38
|
+
# Dummy argument since this method is meant to be used either
|
|
39
|
+
# - to compute potential, then only S is needed and early_dot_product_normals is irrelevant,
|
|
40
|
+
# - to compute velocity, then the adjoint full gradient is needed and early_dot_product is False and this value is unused.
|
|
41
|
+
# TODO: add an only_S argument and return an error here if (early_dot_product and not only_S)
|
|
42
|
+
|
|
43
|
+
else:
|
|
44
|
+
raise ValueError(f"Unrecognized first input for {self.__class__.__name__}.evaluate:\n{mesh1}")
|
|
45
|
+
|
|
46
|
+
return collocation_points, early_dot_product_normals
|
|
47
|
+
|
|
48
|
+
def _init_matrices(self, shape, early_dot_product):
|
|
49
|
+
if self.floating_point_precision == "float32":
|
|
50
|
+
dtype = "complex64"
|
|
51
|
+
elif self.floating_point_precision == "float64":
|
|
52
|
+
dtype = "complex128"
|
|
53
|
+
else:
|
|
54
|
+
raise NotImplementedError(
|
|
55
|
+
f"Unsupported floating point precision: {self.floating_point_precision}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
S = np.zeros(shape, order="F", dtype=dtype)
|
|
59
|
+
K = np.zeros((shape[0], shape[1], 1 if early_dot_product else 3), order="F", dtype=dtype)
|
|
60
|
+
return S, K
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def evaluate(self, mesh1, mesh2, free_surface, water_depth, wavenumber, adjoint_double_layer=True, early_dot_product=True):
|
|
64
|
+
pass
|