capytaine 2.3.1__cp38-cp38-win_amd64.whl → 3.0.0a1__cp38-cp38-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- capytaine/__about__.py +7 -2
- capytaine/__init__.py +10 -14
- capytaine/bem/engines.py +234 -354
- capytaine/bem/problems_and_results.py +14 -13
- capytaine/bem/solver.py +204 -80
- capytaine/bodies/bodies.py +278 -869
- capytaine/bodies/dofs.py +136 -9
- capytaine/bodies/hydrostatics.py +540 -0
- capytaine/bodies/multibodies.py +216 -0
- capytaine/green_functions/{libs/Delhommeau_float32.cp38-win_amd64.dll.a → Delhommeau_float32.cp38-win_amd64.dll.a} +0 -0
- capytaine/green_functions/Delhommeau_float32.cp38-win_amd64.pyd +0 -0
- capytaine/green_functions/{libs/Delhommeau_float64.cp38-win_amd64.dll.a → Delhommeau_float64.cp38-win_amd64.dll.a} +0 -0
- capytaine/green_functions/Delhommeau_float64.cp38-win_amd64.pyd +0 -0
- capytaine/green_functions/abstract_green_function.py +2 -2
- capytaine/green_functions/delhommeau.py +31 -16
- capytaine/green_functions/hams.py +19 -13
- capytaine/io/legacy.py +3 -103
- capytaine/io/xarray.py +11 -6
- capytaine/meshes/__init__.py +2 -6
- capytaine/meshes/abstract_meshes.py +375 -0
- capytaine/meshes/clean.py +302 -0
- capytaine/meshes/clip.py +347 -0
- capytaine/meshes/export.py +89 -0
- capytaine/meshes/geometry.py +244 -394
- capytaine/meshes/io.py +433 -0
- capytaine/meshes/meshes.py +617 -681
- capytaine/meshes/predefined/cylinders.py +22 -56
- capytaine/meshes/predefined/rectangles.py +26 -85
- capytaine/meshes/predefined/spheres.py +4 -11
- capytaine/meshes/quality.py +118 -407
- capytaine/meshes/surface_integrals.py +48 -29
- capytaine/meshes/symmetric_meshes.py +641 -0
- capytaine/meshes/visualization.py +353 -0
- capytaine/post_pro/free_surfaces.py +1 -4
- capytaine/post_pro/kochin.py +10 -10
- capytaine/tools/block_circulant_matrices.py +275 -0
- capytaine/tools/lists_of_points.py +2 -2
- capytaine/tools/memory_monitor.py +45 -0
- capytaine/tools/symbolic_multiplication.py +13 -1
- capytaine/tools/timer.py +58 -34
- capytaine-3.0.0a1.dist-info/DELVEWHEEL +2 -0
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +7 -2
- capytaine-3.0.0a1.dist-info/RECORD +71 -0
- capytaine/bodies/predefined/__init__.py +0 -6
- capytaine/bodies/predefined/cylinders.py +0 -151
- capytaine/bodies/predefined/rectangles.py +0 -111
- capytaine/bodies/predefined/spheres.py +0 -70
- capytaine/green_functions/FinGreen3D/.gitignore +0 -1
- capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +0 -3589
- capytaine/green_functions/FinGreen3D/LICENSE +0 -165
- capytaine/green_functions/FinGreen3D/Makefile +0 -16
- capytaine/green_functions/FinGreen3D/README.md +0 -24
- capytaine/green_functions/FinGreen3D/test_program.f90 +0 -39
- capytaine/green_functions/LiangWuNoblesse/.gitignore +0 -1
- capytaine/green_functions/LiangWuNoblesse/LICENSE +0 -504
- capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +0 -751
- capytaine/green_functions/LiangWuNoblesse/Makefile +0 -16
- capytaine/green_functions/LiangWuNoblesse/README.md +0 -2
- capytaine/green_functions/LiangWuNoblesse/test_program.f90 +0 -28
- capytaine/green_functions/libs/Delhommeau_float32.cp38-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp38-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/__init__.py +0 -0
- capytaine/io/mesh_loaders.py +0 -1086
- capytaine/io/mesh_writers.py +0 -692
- capytaine/io/meshio.py +0 -38
- capytaine/matrices/__init__.py +0 -16
- capytaine/matrices/block.py +0 -592
- capytaine/matrices/block_toeplitz.py +0 -325
- capytaine/matrices/builders.py +0 -89
- capytaine/matrices/linear_solvers.py +0 -232
- capytaine/matrices/low_rank.py +0 -395
- capytaine/meshes/clipper.py +0 -465
- capytaine/meshes/collections.py +0 -342
- capytaine/meshes/mesh_like_protocol.py +0 -37
- capytaine/meshes/properties.py +0 -276
- capytaine/meshes/quadratures.py +0 -80
- capytaine/meshes/symmetric.py +0 -462
- capytaine/tools/lru_cache.py +0 -49
- capytaine/ui/vtk/__init__.py +0 -3
- capytaine/ui/vtk/animation.py +0 -329
- capytaine/ui/vtk/body_viewer.py +0 -28
- capytaine/ui/vtk/helpers.py +0 -82
- capytaine/ui/vtk/mesh_viewer.py +0 -461
- capytaine-2.3.1.dist-info/DELVEWHEEL +0 -2
- capytaine-2.3.1.dist-info/RECORD +0 -98
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
- {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/entry_points.txt +0 -0
- capytaine.libs/{.load-order-capytaine-2.3.1 → .load-order-capytaine-3.0.0a1} +2 -2
capytaine/bodies/bodies.py
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
"""Floating bodies to be used in radiation-diffraction problems."""
|
|
2
2
|
# Copyright (C) 2017-2024 Matthieu Ancellin
|
|
3
3
|
# See LICENSE file at <https://github.com/capytaine/capytaine>
|
|
4
|
+
from __future__ import annotations
|
|
4
5
|
|
|
5
6
|
import logging
|
|
6
7
|
import copy
|
|
7
|
-
from
|
|
8
|
-
from
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from typing import Literal
|
|
9
10
|
|
|
10
11
|
import numpy as np
|
|
11
12
|
import xarray as xr
|
|
12
13
|
|
|
13
|
-
from capytaine.meshes.
|
|
14
|
-
from capytaine.meshes.collections import CollectionOfMeshes
|
|
15
|
-
from capytaine.meshes.geometry import Abstract3DObject, ClippableMixin, Plane, inplace_transformation
|
|
16
|
-
from capytaine.meshes.properties import connected_components, connected_components_of_waterline
|
|
14
|
+
from capytaine.meshes.abstract_meshes import AbstractMesh
|
|
17
15
|
from capytaine.meshes.meshes import Mesh
|
|
18
|
-
from capytaine.meshes.
|
|
19
|
-
from capytaine.bodies.dofs import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
from capytaine.meshes.geometry import connected_components, connected_components_of_waterline
|
|
17
|
+
from capytaine.bodies.dofs import (
|
|
18
|
+
AbstractDof,
|
|
19
|
+
TranslationDof,
|
|
20
|
+
RotationDof,
|
|
21
|
+
DofOnSubmesh,
|
|
22
|
+
normalize_name,
|
|
23
|
+
rigid_body_dofs,
|
|
24
|
+
add_dofs_labels_to_vector,
|
|
25
|
+
add_dofs_labels_to_matrix
|
|
26
|
+
)
|
|
27
|
+
from capytaine.bodies.hydrostatics import _HydrostaticsMixin
|
|
23
28
|
|
|
24
29
|
LOG = logging.getLogger(__name__)
|
|
25
30
|
|
|
26
|
-
TRANSLATION_DOFS_DIRECTIONS = {"surge": (1, 0, 0), "sway": (0, 1, 0), "heave": (0, 0, 1)}
|
|
27
|
-
ROTATION_DOFS_AXIS = {"roll": (1, 0, 0), "pitch": (0, 1, 0), "yaw": (0, 0, 1)}
|
|
28
|
-
|
|
29
31
|
|
|
30
|
-
class FloatingBody(
|
|
32
|
+
class FloatingBody(_HydrostaticsMixin):
|
|
31
33
|
"""A floating body described as a mesh and some degrees of freedom.
|
|
32
34
|
|
|
33
35
|
The mesh structure is stored as a Mesh from capytaine.mesh.mesh or a
|
|
@@ -40,55 +42,65 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
40
42
|
|
|
41
43
|
Parameters
|
|
42
44
|
----------
|
|
43
|
-
mesh :
|
|
45
|
+
mesh : AbstractMesh, optional
|
|
44
46
|
the mesh describing the geometry of the hull of the floating body.
|
|
45
47
|
If none is given, a empty one is created.
|
|
46
|
-
lid_mesh : MeshLike or None, optional
|
|
47
|
-
a mesh of an internal lid for irregular frequencies removal.
|
|
48
|
-
Unlike the mesh of the hull, no dof is defined on the lid_mesh.
|
|
49
|
-
If none is given, none is used when solving the Boundary Integral Equation.
|
|
50
48
|
dofs : dict, optional
|
|
51
49
|
the degrees of freedom of the body.
|
|
52
50
|
If none is given, a empty dictionary is initialized.
|
|
51
|
+
lid_mesh : AbstractMesh or None, optional
|
|
52
|
+
a mesh of an internal lid for irregular frequencies removal.
|
|
53
|
+
Unlike the mesh of the hull, no dof is defined on the lid_mesh.
|
|
54
|
+
If none is given, none is used when solving the Boundary Integral Equation.
|
|
55
|
+
center_of_mass: 3-element array, optional
|
|
56
|
+
the position of the center of mass.
|
|
57
|
+
Required only for some hydrostatics computation.
|
|
53
58
|
mass : float or None, optional
|
|
54
59
|
the mass of the body in kilograms.
|
|
55
60
|
Required only for some hydrostatics computation.
|
|
56
61
|
If None, the mass is implicitly assumed to be the mass of displaced water.
|
|
57
|
-
center_of_mass: 3-element array, optional
|
|
58
|
-
the position of the center of mass.
|
|
59
|
-
Required only for some hydrostatics computation.
|
|
60
62
|
name : str, optional
|
|
61
63
|
a name for the body.
|
|
62
64
|
If none is given, the one of the mesh is used.
|
|
65
|
+
|
|
66
|
+
Attributes
|
|
67
|
+
----------
|
|
68
|
+
mesh_including_lid: AbstractMesh
|
|
69
|
+
The hull mesh joined with the lid mesh
|
|
70
|
+
hull_mask: np.array
|
|
71
|
+
The indices of the faces in mesh_including_lid that are part of the hull
|
|
63
72
|
"""
|
|
64
73
|
|
|
65
|
-
def __init__(self, mesh=None, dofs=None,
|
|
74
|
+
def __init__(self, mesh=None, dofs=None, *, lid_mesh=None, center_of_mass=None, mass=None, name=None):
|
|
66
75
|
if mesh is None:
|
|
67
76
|
self.mesh = Mesh(name="dummy_mesh")
|
|
68
|
-
|
|
69
|
-
elif meshio is not None and isinstance(mesh, meshio._mesh.Mesh):
|
|
70
|
-
from capytaine.io.meshio import load_from_meshio
|
|
71
|
-
self.mesh = load_from_meshio(mesh)
|
|
72
|
-
|
|
73
|
-
elif isinstance(mesh, MeshLike):
|
|
77
|
+
elif isinstance(mesh, AbstractMesh):
|
|
74
78
|
self.mesh = mesh
|
|
75
|
-
|
|
76
79
|
else:
|
|
77
80
|
raise TypeError("Unrecognized `mesh` object passed to the FloatingBody constructor.")
|
|
78
81
|
|
|
79
|
-
if lid_mesh is
|
|
82
|
+
if lid_mesh is None:
|
|
83
|
+
self.lid_mesh = None
|
|
84
|
+
elif isinstance(mesh, AbstractMesh):
|
|
80
85
|
if lid_mesh.nb_faces == 0:
|
|
81
86
|
LOG.warning("Lid mesh %s provided for body initialization is empty. The lid mesh is ignored.", lid_mesh)
|
|
82
87
|
self.lid_mesh = None
|
|
83
88
|
else:
|
|
84
89
|
self.lid_mesh = lid_mesh.with_normal_vector_going_down(inplace=False)
|
|
85
90
|
else:
|
|
86
|
-
|
|
91
|
+
raise TypeError("Unrecognized `lid_mesh` object passed to the FloatingBody constructor.")
|
|
92
|
+
|
|
93
|
+
if self.lid_mesh is None:
|
|
94
|
+
self.mesh_including_lid = self.mesh
|
|
95
|
+
self.hull_mask = np.full((self.mesh.nb_faces,), True)
|
|
96
|
+
else:
|
|
97
|
+
self.mesh_including_lid, masks = self.mesh.join_meshes(self.lid_mesh, return_masks=True)
|
|
98
|
+
self.hull_mask = masks[0]
|
|
87
99
|
|
|
88
100
|
if name is None and mesh is None:
|
|
89
101
|
self.name = "dummy_body"
|
|
90
102
|
elif name is None:
|
|
91
|
-
if hasattr(self.mesh, "name"):
|
|
103
|
+
if hasattr(self.mesh, "name") and self.mesh.name is not None:
|
|
92
104
|
self.name = self.mesh.name
|
|
93
105
|
else:
|
|
94
106
|
self.name = "anonymous_body"
|
|
@@ -101,38 +113,24 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
101
113
|
else:
|
|
102
114
|
self.center_of_mass = None
|
|
103
115
|
|
|
104
|
-
if hasattr(self.mesh, "heal_mesh") and self.mesh.nb_vertices > 0 and self.mesh.nb_faces > 0:
|
|
105
|
-
self.mesh.heal_mesh()
|
|
106
|
-
|
|
107
116
|
if dofs is None:
|
|
108
117
|
self.dofs = {}
|
|
109
|
-
elif isinstance(dofs, RigidBodyDofsPlaceholder):
|
|
110
|
-
if dofs.rotation_center is not None:
|
|
111
|
-
self.rotation_center = np.asarray(dofs.rotation_center, dtype=float)
|
|
112
|
-
self.dofs = {}
|
|
113
|
-
self.add_all_rigid_body_dofs()
|
|
114
118
|
else:
|
|
115
|
-
self.dofs =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
LOG.info(f"New floating body: {self.__str__()}.")
|
|
119
|
+
self.dofs = {
|
|
120
|
+
k: v if isinstance(v, AbstractDof) else np.asarray(v)
|
|
121
|
+
for k, v in dofs.items()
|
|
122
|
+
}
|
|
120
123
|
|
|
121
124
|
self._check_dofs_shape_consistency()
|
|
122
125
|
|
|
123
|
-
|
|
124
|
-
"""Merge the mesh and lid_mesh, while keeping track of where each panel came from."""
|
|
125
|
-
if self.lid_mesh is None:
|
|
126
|
-
self.mesh_including_lid = self.mesh
|
|
127
|
-
self.hull_mask = np.full((self.mesh.nb_faces,), True)
|
|
128
|
-
else:
|
|
129
|
-
self.mesh_including_lid, masks = self.mesh.join_meshes(self.lid_mesh, return_masks=True)
|
|
130
|
-
self.hull_mask = masks[0]
|
|
126
|
+
LOG.debug(f"New floating body: {self.__str__()}.")
|
|
131
127
|
|
|
132
128
|
@staticmethod
|
|
133
129
|
def from_meshio(mesh, name=None) -> 'FloatingBody':
|
|
134
130
|
"""Create a FloatingBody from a meshio mesh object.
|
|
135
131
|
Kinda deprecated, use cpt.load_mesh instead."""
|
|
132
|
+
LOG.warning("Deprecation warning: The method FloatingBody.from_meshio(...) is deprecated. "
|
|
133
|
+
"Please prefer FloatingBody(mesh=cpt.load_mesh(...), ...)")
|
|
136
134
|
from capytaine.io.meshio import load_from_meshio
|
|
137
135
|
return FloatingBody(mesh=load_from_meshio(mesh, name), name=name)
|
|
138
136
|
|
|
@@ -140,8 +138,11 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
140
138
|
def from_file(filename: str, file_format=None, name=None) -> 'FloatingBody':
|
|
141
139
|
"""Create a FloatingBody from a mesh file using meshmagick.
|
|
142
140
|
Kinda deprecated, use cpt.load_mesh instead."""
|
|
141
|
+
LOG.warning("Deprecation warning: The method FloatingBody.from_file(...) is deprecated. "
|
|
142
|
+
"Please prefer FloatingBody(mesh=cpt.load_mesh(...), ...)")
|
|
143
143
|
from capytaine.io.mesh_loaders import load_mesh
|
|
144
|
-
if name is None:
|
|
144
|
+
if name is None:
|
|
145
|
+
name = filename
|
|
145
146
|
mesh = load_mesh(filename, file_format, name=f"{name}_mesh")
|
|
146
147
|
return FloatingBody(mesh, name=name)
|
|
147
148
|
|
|
@@ -158,7 +159,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
158
159
|
"""Number of degrees of freedom."""
|
|
159
160
|
return len(self.dofs)
|
|
160
161
|
|
|
161
|
-
def add_translation_dof(self, direction=None, name=None
|
|
162
|
+
def add_translation_dof(self, direction=None, name=None) -> None:
|
|
162
163
|
"""Add a new translation dof (in place).
|
|
163
164
|
If no direction is given, the code tries to infer it from the name.
|
|
164
165
|
|
|
@@ -168,672 +169,116 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
168
169
|
the direction of the translation
|
|
169
170
|
name : str, optional
|
|
170
171
|
a name for the degree of freedom
|
|
171
|
-
amplitude : float, optional
|
|
172
|
-
amplitude of the dof (default: 1.0 m/s)
|
|
173
172
|
"""
|
|
174
|
-
if direction is None:
|
|
175
|
-
if name is not None and name.lower() in TRANSLATION_DOFS_DIRECTIONS:
|
|
176
|
-
direction = TRANSLATION_DOFS_DIRECTIONS[name.lower()]
|
|
177
|
-
else:
|
|
178
|
-
raise ValueError("A direction needs to be specified for the dof.")
|
|
179
|
-
|
|
180
173
|
if name is None:
|
|
181
174
|
name = f"dof_{self.nb_dofs}_translation"
|
|
175
|
+
if direction is None and normalize_name(name) in {"Surge", "Sway", "Heave"}:
|
|
176
|
+
self.dofs[name] = rigid_body_dofs()[normalize_name(name)]
|
|
177
|
+
else:
|
|
178
|
+
self.dofs[name] = TranslationDof(
|
|
179
|
+
direction=direction,
|
|
180
|
+
)
|
|
182
181
|
|
|
183
|
-
|
|
184
|
-
assert direction.shape == (3,)
|
|
185
|
-
|
|
186
|
-
motion = np.empty((self.mesh.nb_faces, 3))
|
|
187
|
-
motion[:, :] = direction
|
|
188
|
-
self.dofs[name] = amplitude * motion
|
|
189
|
-
|
|
190
|
-
def add_rotation_dof(self, axis=None, name=None, amplitude=1.0) -> None:
|
|
182
|
+
def add_rotation_dof(self, rotation_center=None, direction=None, name=None) -> None:
|
|
191
183
|
"""Add a new rotation dof (in place).
|
|
192
184
|
If no axis is given, the code tries to infer it from the name.
|
|
193
185
|
|
|
194
186
|
Parameters
|
|
195
187
|
----------
|
|
196
|
-
|
|
197
|
-
|
|
188
|
+
rotation_center: array of shape (3,), optional
|
|
189
|
+
One point on the rotation axis
|
|
190
|
+
direction: array of shape (3,), optional
|
|
191
|
+
The direction of the rotation axis
|
|
198
192
|
name : str, optional
|
|
199
193
|
a name for the degree of freedom
|
|
200
|
-
amplitude : float, optional
|
|
201
|
-
amplitude of the dof (default: 1.0)
|
|
202
194
|
"""
|
|
203
|
-
if axis is None:
|
|
204
|
-
if name is not None and name.lower() in ROTATION_DOFS_AXIS:
|
|
205
|
-
axis_direction = ROTATION_DOFS_AXIS[name.lower()]
|
|
206
|
-
for point_attr in ('rotation_center', 'center_of_mass', 'geometric_center'):
|
|
207
|
-
if hasattr(self, point_attr) and getattr(self, point_attr) is not None:
|
|
208
|
-
axis_point = getattr(self, point_attr)
|
|
209
|
-
LOG.info(f"The rotation dof {name} has been initialized around the point: "
|
|
210
|
-
f"{self.__short_str__()}.{point_attr} = {getattr(self, point_attr)}")
|
|
211
|
-
break
|
|
212
|
-
else:
|
|
213
|
-
axis_point = np.array([0, 0, 0])
|
|
214
|
-
LOG.warning(f"The rotation dof {name} has been initialized "
|
|
215
|
-
f"around the origin of the domain (0, 0, 0).")
|
|
216
|
-
else:
|
|
217
|
-
raise ValueError("A direction needs to be specified for the dof.")
|
|
218
|
-
else:
|
|
219
|
-
axis_point = axis.point
|
|
220
|
-
axis_direction = axis.vector
|
|
221
|
-
|
|
222
195
|
if name is None:
|
|
223
196
|
name = f"dof_{self.nb_dofs}_rotation"
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
197
|
+
if rotation_center is None:
|
|
198
|
+
for point_attr in ('rotation_center', 'center_of_mass'):
|
|
199
|
+
if hasattr(self, point_attr) and getattr(self, point_attr) is not None:
|
|
200
|
+
rotation_center = getattr(self, point_attr)
|
|
201
|
+
LOG.info(f"The rotation dof {name} has been initialized around the point: "
|
|
202
|
+
f"{self.__short_str__()}.{point_attr} = {getattr(self, point_attr)}")
|
|
203
|
+
break
|
|
204
|
+
if direction is None and normalize_name(name) in {"Roll", "Pitch", "Yaw"}:
|
|
205
|
+
self.dofs[name] = rigid_body_dofs(rotation_center=rotation_center)[normalize_name(name)]
|
|
227
206
|
else:
|
|
228
|
-
|
|
229
|
-
|
|
207
|
+
self.dofs[name] = RotationDof(
|
|
208
|
+
rotation_center=rotation_center,
|
|
209
|
+
direction=direction,
|
|
210
|
+
)
|
|
230
211
|
|
|
231
|
-
def add_all_rigid_body_dofs(self) -> None:
|
|
212
|
+
def add_all_rigid_body_dofs(self, rotation_center=None) -> None:
|
|
232
213
|
"""Add the six degrees of freedom of rigid bodies (in place)."""
|
|
233
214
|
self.add_translation_dof(name="Surge")
|
|
234
215
|
self.add_translation_dof(name="Sway")
|
|
235
216
|
self.add_translation_dof(name="Heave")
|
|
236
|
-
self.add_rotation_dof(name="Roll")
|
|
237
|
-
self.add_rotation_dof(name="Pitch")
|
|
238
|
-
self.add_rotation_dof(name="Yaw")
|
|
217
|
+
self.add_rotation_dof(rotation_center=rotation_center, name="Roll")
|
|
218
|
+
self.add_rotation_dof(rotation_center=rotation_center, name="Pitch")
|
|
219
|
+
self.add_rotation_dof(rotation_center=rotation_center, name="Yaw")
|
|
239
220
|
|
|
240
221
|
def integrate_pressure(self, pressure):
|
|
241
222
|
forces = {}
|
|
242
223
|
for dof_name in self.dofs:
|
|
224
|
+
if isinstance(self.dofs[dof_name], AbstractDof):
|
|
225
|
+
dof = self.dofs[dof_name].evaluate_motion(self.mesh)
|
|
226
|
+
else:
|
|
227
|
+
dof = self.dofs[dof_name]
|
|
243
228
|
# Scalar product on each face:
|
|
244
|
-
normal_dof_amplitude_on_face = - np.sum(
|
|
229
|
+
normal_dof_amplitude_on_face = - np.sum(dof * self.mesh.faces_normals, axis=1)
|
|
245
230
|
# The minus sign in the above line is because we want the force of the fluid on the body and not the force of the body on the fluid.
|
|
246
231
|
# Sum over all faces:
|
|
247
232
|
forces[dof_name] = np.sum(pressure * normal_dof_amplitude_on_face * self.mesh.faces_areas)
|
|
248
233
|
return forces
|
|
249
234
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
235
|
+
def keep_only_dofs(self, *args, **kwargs):
|
|
236
|
+
raise NotImplementedError("`keep_only_dofs` has been removed. Consider using `body = body.with_only_dofs(['dof_name'])` instead.")
|
|
237
|
+
|
|
238
|
+
def with_only_dofs(self, dofs):
|
|
239
|
+
body = FloatingBody(mesh=self.mesh,
|
|
240
|
+
lid_mesh=self.lid_mesh,
|
|
241
|
+
dofs={k: v for k, v in self.dofs.items() if k in dofs},
|
|
242
|
+
mass=self.mass,
|
|
243
|
+
center_of_mass=self.center_of_mass,
|
|
244
|
+
name=self.name)
|
|
255
245
|
|
|
256
246
|
if hasattr(self, 'inertia_matrix'):
|
|
257
|
-
|
|
247
|
+
body.inertia_matrix = self.inertia_matrix.sel(radiating_dof=dofs, influenced_dof=dofs)
|
|
258
248
|
if hasattr(self, 'hydrostatic_stiffness'):
|
|
259
|
-
|
|
249
|
+
body.hydrostatic_stiffness = self.hydrostatic_stiffness.sel(radiating_dof=dofs, influenced_dof=dofs)
|
|
260
250
|
|
|
261
|
-
return
|
|
251
|
+
return body
|
|
262
252
|
|
|
263
253
|
def add_dofs_labels_to_vector(self, vector):
|
|
264
254
|
"""Helper function turning a bare vector into a vector labelled by the name of the dofs of the body,
|
|
265
255
|
to be used for instance for the computation of RAO."""
|
|
266
|
-
return
|
|
267
|
-
coords={'influenced_dof': list(self.dofs)},
|
|
268
|
-
)
|
|
256
|
+
return add_dofs_labels_to_vector(self.dofs.keys(), vector)
|
|
269
257
|
|
|
270
258
|
def add_dofs_labels_to_matrix(self, matrix):
|
|
271
259
|
"""Helper function turning a bare matrix into a matrix labelled by the name of the dofs of the body,
|
|
272
260
|
to be used for instance for the computation of RAO."""
|
|
273
|
-
return
|
|
274
|
-
coords={'influenced_dof': list(self.dofs), 'radiating_dof': list(self.dofs)},
|
|
275
|
-
)
|
|
261
|
+
return add_dofs_labels_to_matrix(self.dofs.keys(), matrix)
|
|
276
262
|
|
|
277
263
|
def _check_dofs_shape_consistency(self):
|
|
278
264
|
for dof_name, dof in self.dofs.items():
|
|
279
|
-
if
|
|
280
|
-
|
|
265
|
+
if (not isinstance(dof, AbstractDof) and
|
|
266
|
+
(np.array(dof).shape != (self.mesh.nb_faces, 3))):
|
|
267
|
+
raise ValueError(f"The array defining the dof {dof_name} of {self.__short_str__()} does not have the expected shape.\n"
|
|
281
268
|
f"Expected shape: ({self.mesh.nb_faces}, 3)\n"
|
|
282
269
|
f" Actual shape: {dof.shape}")
|
|
283
270
|
|
|
284
271
|
|
|
285
|
-
###################
|
|
286
|
-
# Hydrostatics #
|
|
287
|
-
###################
|
|
288
|
-
|
|
289
|
-
def surface_integral(self, data, **kwargs):
|
|
290
|
-
"""Returns integral of given data along wet surface area."""
|
|
291
|
-
return self.mesh.surface_integral(data, **kwargs)
|
|
292
|
-
|
|
293
|
-
def waterplane_integral(self, data, **kwargs):
|
|
294
|
-
"""Returns integral of given data along water plane area."""
|
|
295
|
-
return self.mesh.waterplane_integral(data, **kwargs)
|
|
296
|
-
|
|
297
|
-
@property
|
|
298
|
-
def wet_surface_area(self):
|
|
299
|
-
"""Returns wet surface area."""
|
|
300
|
-
return self.mesh.wet_surface_area
|
|
301
|
-
|
|
302
|
-
@property
|
|
303
|
-
def volumes(self):
|
|
304
|
-
"""Returns volumes using x, y, z components of the FloatingBody."""
|
|
305
|
-
return self.mesh.volumes
|
|
306
|
-
|
|
307
|
-
@property
|
|
308
|
-
def volume(self):
|
|
309
|
-
"""Returns volume of the FloatingBody."""
|
|
310
|
-
return self.mesh.volume
|
|
311
|
-
|
|
312
|
-
def disp_mass(self, *, rho=1000.0):
|
|
313
|
-
return self.mesh.disp_mass(rho=rho)
|
|
314
|
-
|
|
315
|
-
@property
|
|
316
|
-
def center_of_buoyancy(self):
|
|
317
|
-
"""Returns center of buoyancy of the FloatingBody."""
|
|
318
|
-
return self.mesh.center_of_buoyancy
|
|
319
|
-
|
|
320
|
-
@property
|
|
321
|
-
def waterplane_area(self):
|
|
322
|
-
"""Returns water plane area of the FloatingBody."""
|
|
323
|
-
return self.mesh.waterplane_area
|
|
324
|
-
|
|
325
|
-
@property
|
|
326
|
-
def waterplane_center(self):
|
|
327
|
-
"""Returns water plane center of the FloatingBody.
|
|
328
|
-
|
|
329
|
-
Note: Returns None if the FloatingBody is full submerged.
|
|
330
|
-
"""
|
|
331
|
-
return self.mesh.waterplane_center
|
|
332
|
-
|
|
333
|
-
@property
|
|
334
|
-
def transversal_metacentric_radius(self):
|
|
335
|
-
"""Returns transversal metacentric radius of the mesh."""
|
|
336
|
-
inertia_moment = -self.waterplane_integral(self.mesh.faces_centers[:,1]**2)
|
|
337
|
-
return inertia_moment / self.volume
|
|
338
|
-
|
|
339
|
-
@property
|
|
340
|
-
def longitudinal_metacentric_radius(self):
|
|
341
|
-
"""Returns longitudinal metacentric radius of the mesh."""
|
|
342
|
-
inertia_moment = -self.waterplane_integral(self.mesh.faces_centers[:,0]**2)
|
|
343
|
-
return inertia_moment / self.volume
|
|
344
|
-
|
|
345
|
-
@property
|
|
346
|
-
def transversal_metacentric_height(self):
|
|
347
|
-
"""Returns transversal metacentric height of the mesh."""
|
|
348
|
-
gb = self.center_of_mass - self.center_of_buoyancy
|
|
349
|
-
return self.transversal_metacentric_radius - gb[2]
|
|
350
|
-
|
|
351
|
-
@property
|
|
352
|
-
def longitudinal_metacentric_height(self):
|
|
353
|
-
"""Returns longitudinal metacentric height of the mesh."""
|
|
354
|
-
gb = self.center_of_mass - self.center_of_buoyancy
|
|
355
|
-
return self.longitudinal_metacentric_radius - gb[2]
|
|
356
|
-
|
|
357
|
-
def dof_normals(self, dof):
|
|
358
|
-
"""Returns dot product of the surface face normals and DOF"""
|
|
359
|
-
return np.sum(self.mesh.faces_normals * dof, axis=1)
|
|
360
|
-
|
|
361
|
-
def _infer_rotation_center(self):
|
|
362
|
-
"""Hacky way to infer the point around which the rotation dofs are defined.
|
|
363
|
-
(Assuming all three rotation dofs are defined around the same point).
|
|
364
|
-
In the future, should be replaced by something more robust.
|
|
365
|
-
"""
|
|
366
|
-
if hasattr(self, "rotation_center"):
|
|
367
|
-
return np.asarray(self.rotation_center)
|
|
368
|
-
|
|
369
|
-
else:
|
|
370
|
-
try:
|
|
371
|
-
xc1 = self.dofs["Pitch"][:, 2] + self.mesh.faces_centers[:, 0]
|
|
372
|
-
xc2 = -self.dofs["Yaw"][:, 1] + self.mesh.faces_centers[:, 0]
|
|
373
|
-
yc1 = self.dofs["Yaw"][:, 0] + self.mesh.faces_centers[:, 1]
|
|
374
|
-
yc2 = -self.dofs["Roll"][:, 2] + self.mesh.faces_centers[:, 1]
|
|
375
|
-
zc1 = -self.dofs["Pitch"][:, 0] + self.mesh.faces_centers[:, 2]
|
|
376
|
-
zc2 = self.dofs["Roll"][:, 1] + self.mesh.faces_centers[:, 2]
|
|
377
|
-
|
|
378
|
-
# All items should be identical in a given vector
|
|
379
|
-
assert np.isclose(xc1, xc1[0]).all()
|
|
380
|
-
assert np.isclose(yc1, yc1[0]).all()
|
|
381
|
-
assert np.isclose(zc1, zc1[0]).all()
|
|
382
|
-
|
|
383
|
-
# Both vector should be identical
|
|
384
|
-
assert np.allclose(xc1, xc2)
|
|
385
|
-
assert np.allclose(yc1, yc2)
|
|
386
|
-
assert np.allclose(zc1, zc2)
|
|
387
|
-
|
|
388
|
-
return np.array([xc1[0], yc1[0], zc1[0]])
|
|
389
|
-
|
|
390
|
-
except Exception as e:
|
|
391
|
-
raise ValueError(
|
|
392
|
-
f"Failed to infer the rotation center of {self.name} to compute rigid body hydrostatics.\n"
|
|
393
|
-
f"Possible fix: add a `rotation_center` attribute to {self.name}.\n"
|
|
394
|
-
"Note that rigid body hydrostatic methods currently assume that the three rotation dofs have the same rotation center."
|
|
395
|
-
) from e
|
|
396
|
-
|
|
397
|
-
def each_hydrostatic_stiffness(self, influenced_dof_name, radiating_dof_name, *,
|
|
398
|
-
influenced_dof_div=0.0, rho=1000.0, g=9.81):
|
|
399
|
-
r"""
|
|
400
|
-
Return the hydrostatic stiffness for a pair of DOFs.
|
|
401
|
-
|
|
402
|
-
:math:`C_{ij} = \rho g\iint_S (\hat{n} \cdot V_j) (w_i + z D_i) dS`
|
|
403
|
-
|
|
404
|
-
where :math:`\hat{n}` is surface normal,
|
|
405
|
-
|
|
406
|
-
:math:`V_i = u_i \hat{n}_x + v_i \hat{n}_y + w_i \hat{n}_z` is DOF vector and
|
|
407
|
-
|
|
408
|
-
:math:`D_i = \nabla \cdot V_i` is the divergence of the DOF.
|
|
409
|
-
|
|
410
|
-
Parameters
|
|
411
|
-
----------
|
|
412
|
-
influenced_dof_name : str
|
|
413
|
-
Name of influenced DOF vector of the FloatingBody
|
|
414
|
-
radiating_dof_name: str
|
|
415
|
-
Name of radiating DOF vector of the FloatingBody
|
|
416
|
-
influenced_dof_div: np.ndarray (Face_count), optional
|
|
417
|
-
Influenced DOF divergence of the FloatingBody, by default 0.0.
|
|
418
|
-
rho: float, optional
|
|
419
|
-
water density, by default 1000.0
|
|
420
|
-
g: float, optional
|
|
421
|
-
Gravity acceleration, by default 9.81
|
|
422
|
-
|
|
423
|
-
Returns
|
|
424
|
-
-------
|
|
425
|
-
hs_ij: xarray.variable
|
|
426
|
-
hydrostatic_stiffness of ith DOF and jth DOF.
|
|
427
|
-
|
|
428
|
-
Note
|
|
429
|
-
----
|
|
430
|
-
This function computes the hydrostatic stiffness assuming :math:`D_{i} = 0`.
|
|
431
|
-
If :math:`D_i \neq 0`, input the divergence interpolated to face centers.
|
|
432
|
-
|
|
433
|
-
General integral equations are used for the rigid body modes and
|
|
434
|
-
Neumann (1994) method is used for flexible modes.
|
|
435
|
-
|
|
436
|
-
References
|
|
437
|
-
----------
|
|
438
|
-
Newman, John Nicholas. "Wave effects on deformable bodies."Applied ocean
|
|
439
|
-
research" 16.1 (1994): 47-59.
|
|
440
|
-
http://resolver.tudelft.nl/uuid:0adff84c-43c7-43aa-8cd8-d4c44240bed8
|
|
441
|
-
|
|
442
|
-
"""
|
|
443
|
-
# Newman (1994) formula is not 'complete' as recovering the rigid body
|
|
444
|
-
# terms is not possible. https://doi.org/10.1115/1.3058702.
|
|
445
|
-
|
|
446
|
-
# Alternative is to use the general equation of hydrostatic and
|
|
447
|
-
# restoring coefficient for rigid modes and use Newman equation for elastic
|
|
448
|
-
# modes.
|
|
449
|
-
|
|
450
|
-
rigid_dof_names = ("Surge", "Sway", "Heave", "Roll", "Pitch", "Yaw")
|
|
451
|
-
dof_pair = (influenced_dof_name, radiating_dof_name)
|
|
452
|
-
|
|
453
|
-
if set(dof_pair).issubset(set(rigid_dof_names)):
|
|
454
|
-
if self.center_of_mass is None:
|
|
455
|
-
raise ValueError(f"Trying to compute rigid-body hydrostatic stiffness for {self.name}, but no center of mass has been defined.\n"
|
|
456
|
-
f"Suggested solution: define a `center_of_mass` attribute for the FloatingBody {self.name}.")
|
|
457
|
-
mass = self.disp_mass(rho=rho) if self.mass is None else self.mass
|
|
458
|
-
xc, yc, zc = self._infer_rotation_center()
|
|
459
|
-
|
|
460
|
-
if dof_pair == ("Heave", "Heave"):
|
|
461
|
-
norm_hs_stiff = self.waterplane_area
|
|
462
|
-
elif dof_pair in [("Heave", "Roll"), ("Roll", "Heave")]:
|
|
463
|
-
norm_hs_stiff = -self.waterplane_integral(self.mesh.faces_centers[:,1] - yc)
|
|
464
|
-
elif dof_pair in [("Heave", "Pitch"), ("Pitch", "Heave")]:
|
|
465
|
-
norm_hs_stiff = self.waterplane_integral(self.mesh.faces_centers[:,0] - xc)
|
|
466
|
-
elif dof_pair == ("Roll", "Roll"):
|
|
467
|
-
norm_hs_stiff = (
|
|
468
|
-
-self.waterplane_integral((self.mesh.faces_centers[:,1] - yc)**2)
|
|
469
|
-
+ self.volume*(self.center_of_buoyancy[2] - zc) - mass/rho*(self.center_of_mass[2] - zc)
|
|
470
|
-
)
|
|
471
|
-
elif dof_pair in [("Roll", "Pitch"), ("Pitch", "Roll")]:
|
|
472
|
-
norm_hs_stiff = self.waterplane_integral((self.mesh.faces_centers[:,0] - xc)
|
|
473
|
-
* (self.mesh.faces_centers[:,1] - yc))
|
|
474
|
-
elif dof_pair == ("Roll", "Yaw"):
|
|
475
|
-
norm_hs_stiff = - self.volume*(self.center_of_buoyancy[0] - xc) + mass/rho*(self.center_of_mass[0] - xc)
|
|
476
|
-
elif dof_pair == ("Pitch", "Pitch"):
|
|
477
|
-
norm_hs_stiff = (
|
|
478
|
-
-self.waterplane_integral((self.mesh.faces_centers[:,0] - xc)**2)
|
|
479
|
-
+ self.volume*(self.center_of_buoyancy[2] - zc) - mass/rho*(self.center_of_mass[2] - zc)
|
|
480
|
-
)
|
|
481
|
-
elif dof_pair == ("Pitch", "Yaw"):
|
|
482
|
-
norm_hs_stiff = - self.volume*(self.center_of_buoyancy[1] - yc) + mass/rho*(self.center_of_mass[1] - yc)
|
|
483
|
-
else:
|
|
484
|
-
norm_hs_stiff = 0.0
|
|
485
|
-
else:
|
|
486
|
-
if self.mass is not None and not np.isclose(self.mass, self.disp_mass(rho=rho), rtol=1e-4):
|
|
487
|
-
raise NotImplementedError(
|
|
488
|
-
f"Trying to compute the hydrostatic stiffness for dofs {radiating_dof_name} and {influenced_dof_name}"
|
|
489
|
-
f"of body {self.name}, which is not neutrally buoyant (mass={self.mass}, disp_mass={self.disp_mass(rho=rho)}).\n"
|
|
490
|
-
f"This case has not been implemented in Capytaine. You need either a single rigid body or a neutrally buoyant body."
|
|
491
|
-
)
|
|
492
|
-
|
|
493
|
-
# Newman (1994) formula for flexible DOFs
|
|
494
|
-
influenced_dof = np.array(self.dofs[influenced_dof_name])
|
|
495
|
-
radiating_dof = np.array(self.dofs[radiating_dof_name])
|
|
496
|
-
influenced_dof_div_array = np.array(influenced_dof_div)
|
|
497
|
-
|
|
498
|
-
radiating_dof_normal = self.dof_normals(radiating_dof)
|
|
499
|
-
z_influenced_dof_div = influenced_dof[:,2] + self.mesh.faces_centers[:,2] * influenced_dof_div_array
|
|
500
|
-
norm_hs_stiff = self.surface_integral( -radiating_dof_normal * z_influenced_dof_div)
|
|
501
|
-
|
|
502
|
-
hs_stiff = rho * g * norm_hs_stiff
|
|
503
|
-
|
|
504
|
-
return xr.DataArray([[hs_stiff]],
|
|
505
|
-
dims=['influenced_dof', 'radiating_dof'],
|
|
506
|
-
coords={'influenced_dof': [influenced_dof_name],
|
|
507
|
-
'radiating_dof': [radiating_dof_name]},
|
|
508
|
-
name="hydrostatic_stiffness"
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
def compute_hydrostatic_stiffness(self, *, divergence=None, rho=1000.0, g=9.81):
|
|
512
|
-
r"""
|
|
513
|
-
Compute hydrostatic stiffness matrix for all DOFs of the body.
|
|
514
|
-
|
|
515
|
-
:math:`C_{ij} = \rho g\iint_S (\hat{n} \cdot V_j) (w_i + z D_i) dS`
|
|
516
|
-
|
|
517
|
-
where :math:`\hat{n}` is surface normal,
|
|
518
|
-
|
|
519
|
-
:math:`V_i = u_i \hat{n}_x + v_i \hat{n}_y + w_i \hat{n}_z` is DOF vector and
|
|
520
|
-
|
|
521
|
-
:math:`D_i = \nabla \cdot V_i` is the divergence of the DOF.
|
|
522
|
-
|
|
523
|
-
Parameters
|
|
524
|
-
----------
|
|
525
|
-
divergence : dict mapping a dof name to an array of shape (nb_faces) or
|
|
526
|
-
xarray.DataArray of shape (nb_dofs × nb_faces), optional
|
|
527
|
-
Divergence of the DOFs, by default None
|
|
528
|
-
rho : float, optional
|
|
529
|
-
Water density, by default 1000.0
|
|
530
|
-
g: float, optional
|
|
531
|
-
Gravity acceleration, by default 9.81
|
|
532
|
-
|
|
533
|
-
Returns
|
|
534
|
-
-------
|
|
535
|
-
xr.DataArray
|
|
536
|
-
Matrix of hydrostatic stiffness
|
|
537
|
-
|
|
538
|
-
Note
|
|
539
|
-
----
|
|
540
|
-
This function computes the hydrostatic stiffness assuming :math:`D_{i} = 0`.
|
|
541
|
-
If :math:`D_i \neq 0`, input the divergence interpolated to face centers.
|
|
542
|
-
|
|
543
|
-
General integral equations are used for the rigid body modes and
|
|
544
|
-
Neumann (1994) method is used for flexible modes.
|
|
545
|
-
|
|
546
|
-
References
|
|
547
|
-
----------
|
|
548
|
-
Newman, John Nicholas. "Wave effects on deformable bodies."Applied ocean
|
|
549
|
-
research" 16.1 (1994): 47-59.
|
|
550
|
-
http://resolver.tudelft.nl/uuid:0adff84c-43c7-43aa-8cd8-d4c44240bed8
|
|
551
|
-
|
|
552
|
-
"""
|
|
553
|
-
if len(self.dofs) == 0:
|
|
554
|
-
raise AttributeError("Cannot compute hydrostatics stiffness on {} since no dof has been defined.".format(self.name))
|
|
555
|
-
|
|
556
|
-
def divergence_dof(influenced_dof):
|
|
557
|
-
if influenced_dof.lower() in [*TRANSLATION_DOFS_DIRECTIONS, *ROTATION_DOFS_AXIS]:
|
|
558
|
-
return 0.0 # Dummy value that is not actually used afterwards.
|
|
559
|
-
elif divergence is None:
|
|
560
|
-
return 0.0
|
|
561
|
-
elif isinstance(divergence, dict) and influenced_dof in divergence.keys():
|
|
562
|
-
return divergence[influenced_dof]
|
|
563
|
-
elif isinstance(divergence, xr.DataArray) and influenced_dof in divergence.coords["influenced_dof"]:
|
|
564
|
-
return divergence.sel(influenced_dof=influenced_dof).values
|
|
565
|
-
else:
|
|
566
|
-
LOG.warning("Computing hydrostatic stiffness without the divergence of {}".format(influenced_dof))
|
|
567
|
-
return 0.0
|
|
568
|
-
|
|
569
|
-
hs_set = xr.merge([
|
|
570
|
-
self.each_hydrostatic_stiffness(
|
|
571
|
-
influenced_dof_name, radiating_dof_name,
|
|
572
|
-
influenced_dof_div = divergence_dof(influenced_dof_name),
|
|
573
|
-
rho=rho, g=g
|
|
574
|
-
)
|
|
575
|
-
for radiating_dof_name in self.dofs
|
|
576
|
-
for influenced_dof_name in self.dofs
|
|
577
|
-
], compat='no_conflicts', join="outer")
|
|
578
|
-
|
|
579
|
-
# Reorder dofs
|
|
580
|
-
K = hs_set.hydrostatic_stiffness.sel(influenced_dof=list(self.dofs.keys()), radiating_dof=list(self.dofs.keys()))
|
|
581
|
-
return K
|
|
582
|
-
|
|
583
|
-
def compute_rigid_body_inertia(self, *, rho=1000.0, output_type="body_dofs"):
|
|
584
|
-
"""
|
|
585
|
-
Inertia Mass matrix of the body for 6 rigid DOFs.
|
|
586
|
-
|
|
587
|
-
Parameters
|
|
588
|
-
----------
|
|
589
|
-
rho : float, optional
|
|
590
|
-
Density of water, by default 1000.0
|
|
591
|
-
output_type : {"body_dofs", "rigid_dofs", "all_dofs"}
|
|
592
|
-
Type of DOFs for mass mat output, by default "body_dofs".
|
|
593
|
-
|
|
594
|
-
Returns
|
|
595
|
-
-------
|
|
596
|
-
xarray.DataArray
|
|
597
|
-
Inertia matrix
|
|
598
|
-
|
|
599
|
-
Raises
|
|
600
|
-
------
|
|
601
|
-
ValueError
|
|
602
|
-
If output_type is not in {"body_dofs", "rigid_dofs", "all_dofs"}.
|
|
603
|
-
"""
|
|
604
|
-
if self.center_of_mass is None:
|
|
605
|
-
raise ValueError(f"Trying to compute rigid-body inertia matrix for {self.name}, but no center of mass has been defined.\n"
|
|
606
|
-
f"Suggested solution: define a `center_of_mass` attribute for the FloatingBody {self.name}.")
|
|
607
|
-
|
|
608
|
-
rc = self._infer_rotation_center()
|
|
609
|
-
fcs = (self.mesh.faces_centers - rc).T
|
|
610
|
-
combinations = np.array([fcs[0]**2, fcs[1]**2, fcs[2]**2, fcs[0]*fcs[1],
|
|
611
|
-
fcs[1]*fcs[2], fcs[2]*fcs[0]])
|
|
612
|
-
integrals = np.array([
|
|
613
|
-
[np.sum(normal_i * fcs[axis] * combination * self.mesh.faces_areas)
|
|
614
|
-
for combination in combinations]
|
|
615
|
-
for axis, normal_i in enumerate(self.mesh.faces_normals.T)])
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
inertias = np.array([
|
|
619
|
-
(integrals[0,1] + integrals[0,2] + integrals[1,1]/3
|
|
620
|
-
+ integrals[1,2] + integrals[2,1] + integrals[2,2]/3)/3,
|
|
621
|
-
(integrals[0,0]/3 + integrals[0,2] + integrals[1,0]
|
|
622
|
-
+ integrals[1,2] + integrals[2,0] + integrals[2,2]/3)/3,
|
|
623
|
-
(integrals[0,0]/3 + integrals[0,1] + integrals[1,0]
|
|
624
|
-
+ integrals[1,1]/3 + integrals[2,0] + integrals[2,1] )/3,
|
|
625
|
-
integrals[2,3],
|
|
626
|
-
integrals[0,4],
|
|
627
|
-
integrals[1,5]
|
|
628
|
-
])
|
|
629
|
-
|
|
630
|
-
cog = self.center_of_mass - rc
|
|
631
|
-
volume = self.volume
|
|
632
|
-
volumic_inertia_matrix = np.array([
|
|
633
|
-
[ volume , 0 , 0 ,
|
|
634
|
-
0 , volume*cog[2] , -volume*cog[1] ],
|
|
635
|
-
[ 0 , volume , 0 ,
|
|
636
|
-
-volume*cog[2] , 0 , volume*cog[0] ],
|
|
637
|
-
[ 0 , 0 , volume ,
|
|
638
|
-
volume*cog[1] , -volume*cog[0] , 0 ] ,
|
|
639
|
-
[ 0 , -volume*cog[2] , volume*cog[1] ,
|
|
640
|
-
inertias[0] , -inertias[3] , -inertias[5] ],
|
|
641
|
-
[ volume*cog[2] , 0 , -volume*cog[0] ,
|
|
642
|
-
-inertias[3] , inertias[1] , -inertias[4] ],
|
|
643
|
-
[-volume*cog[1] , volume*cog[0] , 0 ,
|
|
644
|
-
-inertias[5] , -inertias[4] , inertias[2] ],
|
|
645
|
-
])
|
|
646
|
-
|
|
647
|
-
density = rho if self.mass is None else self.mass/volume
|
|
648
|
-
inertia_matrix = density * volumic_inertia_matrix
|
|
649
|
-
|
|
650
|
-
# Rigid DOFs
|
|
651
|
-
rigid_dof_names = ["Surge", "Sway", "Heave", "Roll", "Pitch", "Yaw"]
|
|
652
|
-
rigid_inertia_matrix_xr = xr.DataArray(data=np.asarray(inertia_matrix),
|
|
653
|
-
dims=['influenced_dof', 'radiating_dof'],
|
|
654
|
-
coords={'influenced_dof': rigid_dof_names,
|
|
655
|
-
'radiating_dof': rigid_dof_names},
|
|
656
|
-
name="inertia_matrix")
|
|
657
|
-
|
|
658
|
-
# Body DOFs (Default as np.nan)
|
|
659
|
-
body_dof_names = list(self.dofs)
|
|
660
|
-
body_dof_count = len(body_dof_names)
|
|
661
|
-
other_dofs_inertia_matrix_xr = xr.DataArray(np.nan * np.zeros([body_dof_count, body_dof_count]),
|
|
662
|
-
dims=['influenced_dof', 'radiating_dof'],
|
|
663
|
-
coords={'influenced_dof': body_dof_names,
|
|
664
|
-
'radiating_dof': body_dof_names},
|
|
665
|
-
name="inertia_matrix")
|
|
666
|
-
|
|
667
|
-
total_mass_xr = xr.merge([rigid_inertia_matrix_xr, other_dofs_inertia_matrix_xr], compat="override", join="outer").inertia_matrix
|
|
668
|
-
|
|
669
|
-
non_rigid_dofs = set(body_dof_names) - set(rigid_dof_names)
|
|
670
|
-
|
|
671
|
-
if output_type == "body_dofs":
|
|
672
|
-
if len(non_rigid_dofs) > 0:
|
|
673
|
-
LOG.warning(f"Non-rigid dofs {non_rigid_dofs} detected: their \
|
|
674
|
-
inertia coefficients are assigned as NaN.")
|
|
675
|
-
|
|
676
|
-
inertia_matrix_xr = total_mass_xr.sel(influenced_dof=body_dof_names,
|
|
677
|
-
radiating_dof=body_dof_names)
|
|
678
|
-
elif output_type == "rigid_dofs":
|
|
679
|
-
inertia_matrix_xr = total_mass_xr.sel(influenced_dof=rigid_dof_names,
|
|
680
|
-
radiating_dof=rigid_dof_names)
|
|
681
|
-
elif output_type == "all_dofs":
|
|
682
|
-
if len(non_rigid_dofs) > 0:
|
|
683
|
-
LOG.warning("Non-rigid dofs: {non_rigid_dofs} are detected and \
|
|
684
|
-
respective inertia coefficients are assigned as NaN.")
|
|
685
|
-
|
|
686
|
-
inertia_matrix_xr = total_mass_xr
|
|
687
|
-
else:
|
|
688
|
-
raise ValueError(f"output_type should be either 'body_dofs', \
|
|
689
|
-
'all_dofs' or 'rigid_dofs'. Given output_type = '{output_type}'.")
|
|
690
|
-
|
|
691
|
-
return inertia_matrix_xr
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
def compute_hydrostatics(self, *, rho=1000.0, g=9.81, divergence=None):
|
|
695
|
-
"""Compute hydrostatics of the FloatingBody.
|
|
696
|
-
|
|
697
|
-
Parameters
|
|
698
|
-
----------
|
|
699
|
-
rho : float, optional
|
|
700
|
-
Density of Water. The default is 1000.
|
|
701
|
-
g: float, optional
|
|
702
|
-
Gravity acceleration. The default is 9.81.
|
|
703
|
-
divergence : np.ndarray, optional
|
|
704
|
-
Divergence of the DOFs.
|
|
705
|
-
|
|
706
|
-
Returns
|
|
707
|
-
-------
|
|
708
|
-
hydrostatics : dict
|
|
709
|
-
All hydrostatics values of the FloatingBody.
|
|
710
|
-
"""
|
|
711
|
-
if self.center_of_mass is None:
|
|
712
|
-
raise ValueError(f"Trying to compute hydrostatics for {self.name}, but no center of mass has been defined.\n"
|
|
713
|
-
f"Suggested solution: define a `center_of_mass` attribute for the FloatingBody {self.name}.")
|
|
714
|
-
|
|
715
|
-
immersed_self = self.immersed_part()
|
|
716
|
-
|
|
717
|
-
full_mesh_vertices = self.mesh.vertices
|
|
718
|
-
coord_max = full_mesh_vertices.max(axis=0)
|
|
719
|
-
coord_min = full_mesh_vertices.min(axis=0)
|
|
720
|
-
full_length, full_breadth, depth = full_mesh_vertices.max(axis=0) - full_mesh_vertices.min(axis=0)
|
|
721
|
-
|
|
722
|
-
vertices = immersed_self.mesh.vertices
|
|
723
|
-
sub_length, sub_breadth, _ = vertices.max(axis=0) - vertices.min(axis=0)
|
|
724
|
-
|
|
725
|
-
if abs(immersed_self.waterplane_area) > 1e-10:
|
|
726
|
-
water_plane_idx = np.isclose(vertices[:,2], 0.0)
|
|
727
|
-
water_plane = vertices[water_plane_idx][:,:-1]
|
|
728
|
-
wl_length, wl_breadth = water_plane.max(axis=0) - water_plane.min(axis=0)
|
|
729
|
-
else:
|
|
730
|
-
wl_length, wl_breadth = 0.0, 0.0
|
|
731
|
-
|
|
732
|
-
hydrostatics = {}
|
|
733
|
-
hydrostatics["g"] = g
|
|
734
|
-
hydrostatics["rho"] = rho
|
|
735
|
-
hydrostatics["center_of_mass"] = self.center_of_mass
|
|
736
|
-
|
|
737
|
-
hydrostatics["wet_surface_area"] = immersed_self.wet_surface_area
|
|
738
|
-
hydrostatics["disp_volumes"] = immersed_self.volumes
|
|
739
|
-
hydrostatics["disp_volume"] = immersed_self.volume
|
|
740
|
-
hydrostatics["disp_mass"] = immersed_self.disp_mass(rho=rho)
|
|
741
|
-
hydrostatics["center_of_buoyancy"] = immersed_self.center_of_buoyancy
|
|
742
|
-
hydrostatics["waterplane_center"] = np.append(immersed_self.waterplane_center, 0.0)
|
|
743
|
-
hydrostatics["waterplane_area"] = immersed_self.waterplane_area
|
|
744
|
-
hydrostatics["transversal_metacentric_radius"] = immersed_self.transversal_metacentric_radius
|
|
745
|
-
hydrostatics["longitudinal_metacentric_radius"] = immersed_self.longitudinal_metacentric_radius
|
|
746
|
-
hydrostatics["transversal_metacentric_height"] = immersed_self.transversal_metacentric_height
|
|
747
|
-
hydrostatics["longitudinal_metacentric_height"] = immersed_self.longitudinal_metacentric_height
|
|
748
|
-
self.hydrostatic_stiffness = hydrostatics["hydrostatic_stiffness"] = immersed_self.compute_hydrostatic_stiffness(
|
|
749
|
-
divergence=divergence, rho=rho, g=g)
|
|
750
|
-
|
|
751
|
-
hydrostatics["length_overall"] = full_length
|
|
752
|
-
hydrostatics["breadth_overall"] = full_breadth
|
|
753
|
-
hydrostatics["depth"] = depth
|
|
754
|
-
hydrostatics["draught"] = np.abs(coord_min[2])
|
|
755
|
-
hydrostatics["length_at_waterline"] = wl_length
|
|
756
|
-
hydrostatics["breadth_at_waterline"] = wl_breadth
|
|
757
|
-
hydrostatics["length_overall_submerged"] = sub_length
|
|
758
|
-
hydrostatics["breadth_overall_submerged"] = sub_breadth
|
|
759
|
-
if any(dof.lower() in {"surge", "sway", "heave", "roll", "pitch", "yaw"}
|
|
760
|
-
for dof in self.dofs) > 0: # If there is at least one rigid body dof:
|
|
761
|
-
self.inertia_matrix = hydrostatics["inertia_matrix"] = self.compute_rigid_body_inertia(rho=rho)
|
|
762
|
-
|
|
763
|
-
return hydrostatics
|
|
764
|
-
|
|
765
|
-
|
|
766
272
|
###################
|
|
767
273
|
# Transformations #
|
|
768
274
|
###################
|
|
769
275
|
|
|
770
|
-
def __add__(self, body_to_add: 'FloatingBody')
|
|
276
|
+
def __add__(self, body_to_add: 'FloatingBody'):
|
|
771
277
|
return self.join_bodies(body_to_add)
|
|
772
278
|
|
|
773
|
-
def join_bodies(*bodies, name=None)
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
meshes = CollectionOfMeshes(
|
|
777
|
-
[body.mesh.copy() for body in bodies],
|
|
778
|
-
name=f"{name}_mesh"
|
|
779
|
-
)
|
|
780
|
-
if all(body.lid_mesh is None for body in bodies):
|
|
781
|
-
lid_meshes = None
|
|
782
|
-
else:
|
|
783
|
-
lid_meshes = CollectionOfMeshes(
|
|
784
|
-
[body.lid_mesh.copy() for body in bodies if body.lid_mesh is not None],
|
|
785
|
-
name=f"{name}_lid_mesh"
|
|
786
|
-
)
|
|
787
|
-
dofs = FloatingBody.combine_dofs(bodies)
|
|
788
|
-
|
|
789
|
-
if all(body.mass is not None for body in bodies):
|
|
790
|
-
new_mass = sum(body.mass for body in bodies)
|
|
791
|
-
else:
|
|
792
|
-
new_mass = None
|
|
793
|
-
|
|
794
|
-
if (all(body.mass is not None for body in bodies)
|
|
795
|
-
and all(body.center_of_mass is not None for body in bodies)):
|
|
796
|
-
new_cog = sum(body.mass*np.asarray(body.center_of_mass) for body in bodies)/new_mass
|
|
797
|
-
else:
|
|
798
|
-
new_cog = None
|
|
799
|
-
|
|
800
|
-
joined_bodies = FloatingBody(
|
|
801
|
-
mesh=meshes, lid_mesh=lid_meshes, dofs=dofs,
|
|
802
|
-
mass=new_mass, center_of_mass=new_cog, name=name
|
|
803
|
-
)
|
|
804
|
-
|
|
805
|
-
for matrix_name in ["inertia_matrix", "hydrostatic_stiffness"]:
|
|
806
|
-
if all(hasattr(body, matrix_name) for body in bodies):
|
|
807
|
-
from scipy.linalg import block_diag
|
|
808
|
-
setattr(joined_bodies, matrix_name, joined_bodies.add_dofs_labels_to_matrix(
|
|
809
|
-
block_diag(*[getattr(body, matrix_name) for body in bodies])
|
|
810
|
-
))
|
|
811
|
-
|
|
812
|
-
return joined_bodies
|
|
813
|
-
|
|
814
|
-
@staticmethod
|
|
815
|
-
def combine_dofs(bodies) -> dict:
|
|
816
|
-
"""Combine the degrees of freedom of several bodies."""
|
|
817
|
-
for body in bodies:
|
|
818
|
-
body._check_dofs_shape_consistency()
|
|
819
|
-
dofs = {}
|
|
820
|
-
cum_nb_faces = accumulate(chain([0], (body.mesh.nb_faces for body in bodies)))
|
|
821
|
-
total_nb_faces = sum(body.mesh.nb_faces for body in bodies)
|
|
822
|
-
for body, nbf in zip(bodies, cum_nb_faces):
|
|
823
|
-
# nbf is the cumulative number of faces of the previous subbodies,
|
|
824
|
-
# that is the offset of the indices of the faces of the current body.
|
|
825
|
-
for name, dof in body.dofs.items():
|
|
826
|
-
new_dof = np.zeros((total_nb_faces, 3))
|
|
827
|
-
new_dof[nbf:nbf+len(dof), :] = dof
|
|
828
|
-
if '__' not in name:
|
|
829
|
-
new_dof_name = '__'.join([body.name, name])
|
|
830
|
-
else:
|
|
831
|
-
# The body is probably a combination of bodies already.
|
|
832
|
-
# So for the associativity of the + operation,
|
|
833
|
-
# it is better to keep the same name.
|
|
834
|
-
new_dof_name = name
|
|
835
|
-
dofs[new_dof_name] = new_dof
|
|
836
|
-
return dofs
|
|
279
|
+
def join_bodies(*bodies, name=None):
|
|
280
|
+
from capytaine.bodies.multibodies import Multibody
|
|
281
|
+
return Multibody(bodies, name=name)
|
|
837
282
|
|
|
838
283
|
def copy(self, name=None) -> 'FloatingBody':
|
|
839
284
|
"""Return a deep copy of the body.
|
|
@@ -870,177 +315,185 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
870
315
|
"""
|
|
871
316
|
bodies = (self.translated((i*distance, j*distance, 0), name=f"{i}_{j}") for j in range(nb_bodies[1]) for i in range(nb_bodies[0]))
|
|
872
317
|
array = FloatingBody.join_bodies(*bodies)
|
|
873
|
-
array.mesh = build_regular_array_of_meshes(self.mesh, distance, nb_bodies)
|
|
874
318
|
array.name = f"array_of_{self.name}"
|
|
875
319
|
return array
|
|
876
320
|
|
|
877
321
|
def assemble_arbitrary_array(self, locations:np.ndarray):
|
|
878
|
-
|
|
879
322
|
if not isinstance(locations, np.ndarray):
|
|
880
323
|
raise TypeError('locations must be of type np.ndarray')
|
|
881
324
|
assert locations.shape[1] == 2, 'locations must be of shape nx2, received {:}'.format(locations.shape)
|
|
882
325
|
|
|
883
326
|
fb_list = []
|
|
884
327
|
for idx, li in enumerate(locations):
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
fb1.name = 'arbitrary_array_body{:02d}'.format(idx)
|
|
888
|
-
fb_list.append(fb1)
|
|
889
|
-
|
|
890
|
-
arbitrary_array = fb_list[0].join_bodies(*fb_list[1:])
|
|
328
|
+
fb_list.append(self.translated(np.append(li, 0), name='arbitrary_array_body{:02d}'.format(idx)))
|
|
329
|
+
arbitrary_array = FloatingBody.join_bodies(*fb_list)
|
|
891
330
|
|
|
892
331
|
return arbitrary_array
|
|
893
332
|
|
|
894
|
-
def
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
if isinstance(self.mesh, CollectionOfMeshes):
|
|
900
|
-
raise NotImplementedError # TODO
|
|
901
|
-
|
|
902
|
-
if return_index:
|
|
903
|
-
new_mesh, id_v = self.mesh.extract_faces(id_faces_to_extract, return_index)
|
|
333
|
+
def mirrored(self, plane: Literal['xOz', 'yOz']) -> "FloatingBody":
|
|
334
|
+
if plane == "xOz":
|
|
335
|
+
mirrored_coord = 1
|
|
336
|
+
elif plane == "yOz":
|
|
337
|
+
mirrored_coord = 0
|
|
904
338
|
else:
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
339
|
+
raise ValueError(f"Unsupported value for plane: {plane}")
|
|
340
|
+
def mirror(p):
|
|
341
|
+
if isinstance(p, TranslationDof):
|
|
342
|
+
return TranslationDof(
|
|
343
|
+
direction=mirror(p.direction),
|
|
344
|
+
)
|
|
345
|
+
if isinstance(p, RotationDof):
|
|
346
|
+
return RotationDof(
|
|
347
|
+
rotation_center=mirror(p.rotation_center),
|
|
348
|
+
direction=mirror(p.direction),
|
|
349
|
+
)
|
|
350
|
+
elif isinstance(p, np.ndarray):
|
|
351
|
+
mirrored_p = p.copy()
|
|
352
|
+
mirrored_p[..., mirrored_coord] *= -1
|
|
353
|
+
return p
|
|
354
|
+
mirrored_dofs = {k: mirror(v) for k,v in self.dofs.items()}
|
|
355
|
+
mirrored_self = FloatingBody(
|
|
356
|
+
mesh=self.mesh.mirrored(plane),
|
|
357
|
+
lid_mesh=self.lid_mesh.mirrored(plane) if self.lid_mesh is not None else None,
|
|
358
|
+
dofs=mirrored_dofs,
|
|
359
|
+
center_of_mass=mirror(self.center_of_mass) if self.center_of_mass is not None else None,
|
|
360
|
+
mass=self.mass,
|
|
361
|
+
)
|
|
362
|
+
if hasattr(self, 'rotation_center'):
|
|
363
|
+
mirrored_self.rotation_center = mirror(self.rotation_center)
|
|
364
|
+
return mirrored_self
|
|
365
|
+
|
|
366
|
+
def translated(self, shift, *, name=None) -> "FloatingBody":
|
|
367
|
+
shift = np.asarray(shift)
|
|
368
|
+
def translate_dof(d):
|
|
369
|
+
if isinstance(d, RotationDof):
|
|
370
|
+
return RotationDof(
|
|
371
|
+
rotation_center=d.rotation_center + shift,
|
|
372
|
+
direction=d.direction,
|
|
373
|
+
)
|
|
374
|
+
else:
|
|
375
|
+
return d
|
|
376
|
+
|
|
377
|
+
translated_self = FloatingBody(
|
|
378
|
+
mesh=self.mesh.translated(shift),
|
|
379
|
+
lid_mesh=self.lid_mesh.translated(shift) if self.lid_mesh is not None else None,
|
|
380
|
+
dofs={k: translate_dof(v) for k, v in self.dofs.items()},
|
|
381
|
+
center_of_mass=self.center_of_mass + shift if self.center_of_mass is not None else None,
|
|
382
|
+
mass=self.mass,
|
|
383
|
+
name=name
|
|
384
|
+
)
|
|
385
|
+
if hasattr(self, 'rotation_center'):
|
|
386
|
+
translated_self.rotation_center = self.rotation_center + shift
|
|
387
|
+
return translated_self
|
|
917
388
|
|
|
918
|
-
def
|
|
919
|
-
|
|
920
|
-
corresponding to each sides of the plane."""
|
|
921
|
-
return FloatingBody(mesh=self.mesh.sliced_by_plane(plane),
|
|
922
|
-
lid_mesh=self.lid_mesh.sliced_by_plane(plane)
|
|
923
|
-
if self.lid_mesh is not None else None,
|
|
924
|
-
dofs=self.dofs, name=self.name)
|
|
389
|
+
def translated_x(self, dx: float, *, name=None) -> "FloatingBody":
|
|
390
|
+
return self.translated([dx, 0.0, 0.0], name=name)
|
|
925
391
|
|
|
926
|
-
def
|
|
927
|
-
|
|
392
|
+
def translated_y(self, dy: float, *, name=None) -> "FloatingBody":
|
|
393
|
+
return self.translated([0.0, dy, 0.0], name=name)
|
|
928
394
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
nb_slices: Tuple[int, int, int]
|
|
932
|
-
The number of slices in each of the x, y and z directions.
|
|
933
|
-
Only powers of 2 are supported at the moment.
|
|
395
|
+
def translated_z(self, dz: float, *, name=None) -> "FloatingBody":
|
|
396
|
+
return self.translated([0.0, 0.0, dz], name=name)
|
|
934
397
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
minced_body = minced_body.sliced_by_plane(plane)
|
|
980
|
-
return minced_body
|
|
981
|
-
|
|
982
|
-
@inplace_transformation
|
|
983
|
-
def mirror(self, plane):
|
|
984
|
-
self.mesh.mirror(plane)
|
|
985
|
-
if self.lid_mesh is not None:
|
|
986
|
-
self.lid_mesh.mirror(plane)
|
|
987
|
-
self._evaluate_full_mesh()
|
|
988
|
-
for dof in self.dofs:
|
|
989
|
-
self.dofs[dof] -= 2 * np.outer(np.dot(self.dofs[dof], plane.normal), plane.normal)
|
|
990
|
-
for point_attr in ('geometric_center', 'rotation_center', 'center_of_mass'):
|
|
991
|
-
if point_attr in self.__dict__ and self.__dict__[point_attr] is not None:
|
|
992
|
-
point = np.array(self.__dict__[point_attr])
|
|
993
|
-
shift = - 2 * (np.dot(point, plane.normal) - plane.c) * plane.normal
|
|
994
|
-
self.__dict__[point_attr] = point + shift
|
|
995
|
-
return self
|
|
996
|
-
|
|
997
|
-
@inplace_transformation
|
|
998
|
-
def translate(self, vector, *args, **kwargs):
|
|
999
|
-
self.mesh.translate(vector, *args, **kwargs)
|
|
1000
|
-
if self.lid_mesh is not None:
|
|
1001
|
-
self.lid_mesh.translate(vector, *args, **kwargs)
|
|
1002
|
-
self._evaluate_full_mesh()
|
|
1003
|
-
for point_attr in ('geometric_center', 'rotation_center', 'center_of_mass'):
|
|
1004
|
-
if point_attr in self.__dict__ and self.__dict__[point_attr] is not None:
|
|
1005
|
-
self.__dict__[point_attr] = np.array(self.__dict__[point_attr]) + vector
|
|
1006
|
-
return self
|
|
1007
|
-
|
|
1008
|
-
@inplace_transformation
|
|
1009
|
-
def rotate(self, axis, angle):
|
|
1010
|
-
self.mesh.rotate(axis, angle)
|
|
1011
|
-
if self.lid_mesh is not None:
|
|
1012
|
-
self.lid_mesh.rotate(axis, angle)
|
|
1013
|
-
self._evaluate_full_mesh()
|
|
1014
|
-
for point_attr in ('geometric_center', 'rotation_center', 'center_of_mass'):
|
|
1015
|
-
if point_attr in self.__dict__ and self.__dict__[point_attr] is not None:
|
|
1016
|
-
self.__dict__[point_attr] = axis.rotate_points([self.__dict__[point_attr]], angle)[0, :]
|
|
1017
|
-
for dof in self.dofs:
|
|
1018
|
-
self.dofs[dof] = axis.rotate_vectors(self.dofs[dof], angle)
|
|
1019
|
-
return self
|
|
1020
|
-
|
|
1021
|
-
@inplace_transformation
|
|
1022
|
-
def clip(self, plane):
|
|
1023
|
-
self._check_dofs_shape_consistency()
|
|
398
|
+
def rotated_with_matrix(self, R, *, name=None) -> "FloatingBody":
|
|
399
|
+
def rotate_dof(d):
|
|
400
|
+
if isinstance(d, TranslationDof):
|
|
401
|
+
return TranslationDof(
|
|
402
|
+
direction=d.direction @ R.T,
|
|
403
|
+
)
|
|
404
|
+
elif isinstance(d, RotationDof):
|
|
405
|
+
return RotationDof(
|
|
406
|
+
rotation_center=d.rotation_center @ R.T,
|
|
407
|
+
direction=d.direction @ R.T,
|
|
408
|
+
)
|
|
409
|
+
else:
|
|
410
|
+
return d
|
|
411
|
+
rotated_self = FloatingBody(
|
|
412
|
+
mesh=self.mesh.rotated_with_matrix(R),
|
|
413
|
+
lid_mesh=self.lid_mesh.rotated_with_matrix(R) if self.lid_mesh is not None else None,
|
|
414
|
+
dofs={k: rotate_dof(v) for k, v in self.dofs.items()},
|
|
415
|
+
center_of_mass=self.center_of_mass @ R.T if self.center_of_mass is not None else None,
|
|
416
|
+
mass=self.mass,
|
|
417
|
+
name=name
|
|
418
|
+
)
|
|
419
|
+
if hasattr(self, 'rotation_center'):
|
|
420
|
+
rotated_self.rotation_center = self.rotation_center @ R.T
|
|
421
|
+
return rotated_self
|
|
422
|
+
|
|
423
|
+
def rotated_x(self, angle: float, *, name=None) -> "FloatingBody":
|
|
424
|
+
c, s = np.cos(angle), np.sin(angle)
|
|
425
|
+
R = np.array([[1, 0, 0], [0, c, -s], [0, s, c]])
|
|
426
|
+
return self.rotated_with_matrix(R, name=name)
|
|
427
|
+
|
|
428
|
+
def rotated_y(self, angle: float, *, name=None) -> "FloatingBody":
|
|
429
|
+
c, s = np.cos(angle), np.sin(angle)
|
|
430
|
+
R = np.array([[c, 0, s], [0, 1, 0], [-s, 0, c]])
|
|
431
|
+
return self.rotated_with_matrix(R, name=name)
|
|
432
|
+
|
|
433
|
+
def rotated_z(self, angle: float, *, name=None) -> "FloatingBody":
|
|
434
|
+
c, s = np.cos(angle), np.sin(angle)
|
|
435
|
+
R = np.array([[c, -s, 0], [s, c, 0], [0, 0, 1]])
|
|
436
|
+
return self.rotated_with_matrix(R, name=name)
|
|
437
|
+
|
|
438
|
+
def _apply_on_mesh(self, func, args, kwargs):
|
|
439
|
+
mesh_with_ids = self.mesh.with_metadata(origin_panel=np.arange(self.mesh.nb_faces))
|
|
440
|
+
transformed_mesh = func(mesh_with_ids, *args, **kwargs)
|
|
441
|
+
transformed_mesh, faces_ids = transformed_mesh.pop_metadata("origin_panel")
|
|
1024
442
|
|
|
1025
|
-
# Clip mesh
|
|
1026
|
-
LOG.info(f"Clipping {self.name} with respect to {plane}")
|
|
1027
|
-
self.mesh.clip(plane)
|
|
1028
443
|
if self.lid_mesh is not None:
|
|
1029
|
-
self.lid_mesh
|
|
1030
|
-
if
|
|
1031
|
-
LOG.warning("
|
|
1032
|
-
|
|
1033
|
-
|
|
444
|
+
transformed_lid_mesh = func(self.lid_mesh, *args, **kwargs)
|
|
445
|
+
if transformed_lid_mesh.nb_faces == 0:
|
|
446
|
+
LOG.warning("Empty lid mesh %s has been removed.", self.lid_mesh)
|
|
447
|
+
transformed_lid_mesh = None
|
|
448
|
+
else:
|
|
449
|
+
transformed_lid_mesh = None
|
|
1034
450
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
451
|
+
new_dofs = {}
|
|
452
|
+
for name, dof in self.dofs.items():
|
|
453
|
+
if isinstance(dof, DofOnSubmesh):
|
|
454
|
+
former_mask = np.zeros(self.mesh.nb_faces, dtype=bool)
|
|
455
|
+
former_mask[dof.faces] = True
|
|
456
|
+
new_mask = former_mask[faces_ids]
|
|
457
|
+
new_dofs[name] = DofOnSubmesh(dof.dof, np.where(new_mask)[0])
|
|
458
|
+
elif isinstance(dof, AbstractDof):
|
|
459
|
+
new_dofs[name] = dof
|
|
1040
460
|
else:
|
|
1041
|
-
|
|
1042
|
-
|
|
461
|
+
# dof is an array or array-like
|
|
462
|
+
new_dofs[name] = np.asarray(dof)[faces_ids]
|
|
463
|
+
|
|
464
|
+
return transformed_mesh, transformed_lid_mesh, new_dofs
|
|
1043
465
|
|
|
466
|
+
def clipped(self, *, origin, normal, name=None) -> "FloatingBody":
|
|
467
|
+
clipped_mesh, clipped_lid_mesh, updated_dofs = self._apply_on_mesh(
|
|
468
|
+
self.mesh.__class__.clipped,
|
|
469
|
+
(),
|
|
470
|
+
{'origin': origin, 'normal': normal}
|
|
471
|
+
)
|
|
472
|
+
if name is None:
|
|
473
|
+
name = self.name
|
|
474
|
+
return FloatingBody(
|
|
475
|
+
mesh=clipped_mesh,
|
|
476
|
+
lid_mesh=clipped_lid_mesh,
|
|
477
|
+
dofs=updated_dofs,
|
|
478
|
+
name=name
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
def immersed_part(self, free_surface=0.0, *, sea_bottom=None, water_depth=None, name=None) -> "FloatingBody":
|
|
482
|
+
clipped_mesh, clipped_lid_mesh, updated_dofs = self._apply_on_mesh(
|
|
483
|
+
self.mesh.__class__.immersed_part,
|
|
484
|
+
(free_surface,),
|
|
485
|
+
{'sea_bottom': sea_bottom, 'water_depth': water_depth}
|
|
486
|
+
)
|
|
487
|
+
if name is None:
|
|
488
|
+
name = self.name
|
|
489
|
+
return FloatingBody(
|
|
490
|
+
mesh=clipped_mesh,
|
|
491
|
+
lid_mesh=clipped_lid_mesh,
|
|
492
|
+
dofs=updated_dofs,
|
|
493
|
+
center_of_mass=self.center_of_mass,
|
|
494
|
+
mass=self.mass,
|
|
495
|
+
name=name
|
|
496
|
+
)
|
|
1044
497
|
|
|
1045
498
|
#############
|
|
1046
499
|
# Display #
|
|
@@ -1093,12 +546,11 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
1093
546
|
yield "center_of_mass", tuple(self.center_of_mass)
|
|
1094
547
|
yield "name", self.name
|
|
1095
548
|
|
|
1096
|
-
def show(self, **kwargs):
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
viewer.finalize()
|
|
549
|
+
def show(self, *args, **kwargs):
|
|
550
|
+
return self.mesh.show(*args, **kwargs)
|
|
551
|
+
|
|
552
|
+
def show_pyvista(self, *args, **kwargs):
|
|
553
|
+
return self.mesh.show_pyvista(*args, **kwargs)
|
|
1102
554
|
|
|
1103
555
|
def show_matplotlib(self, *args, **kwargs):
|
|
1104
556
|
return self.mesh.show_matplotlib(*args, **kwargs)
|
|
@@ -1127,6 +579,10 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
1127
579
|
animation._add_actor(self.mesh.merged(), faces_motion=sum(motion[dof_name] * dof for dof_name, dof in self.dofs.items() if dof_name in motion))
|
|
1128
580
|
return animation
|
|
1129
581
|
|
|
582
|
+
#################################
|
|
583
|
+
# Irregular frequencies removal #
|
|
584
|
+
#################################
|
|
585
|
+
|
|
1130
586
|
@property
|
|
1131
587
|
def minimal_computable_wavelength(self):
|
|
1132
588
|
"""For accuracy of the resolution, wavelength should not be smaller than this value."""
|
|
@@ -1172,50 +628,3 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
1172
628
|
omega_comp = np.sqrt(np.pi*g*p/(np.tanh(np.pi*draft*p)))
|
|
1173
629
|
omega = min(omega, omega_comp)
|
|
1174
630
|
return omega
|
|
1175
|
-
|
|
1176
|
-
def cluster_bodies(*bodies, name=None):
|
|
1177
|
-
"""
|
|
1178
|
-
Builds a hierarchical clustering from a group of bodies
|
|
1179
|
-
|
|
1180
|
-
Parameters
|
|
1181
|
-
----------
|
|
1182
|
-
bodies: list
|
|
1183
|
-
a list of bodies
|
|
1184
|
-
name: str, optional
|
|
1185
|
-
a name for the new body
|
|
1186
|
-
|
|
1187
|
-
Returns
|
|
1188
|
-
-------
|
|
1189
|
-
FloatingBody
|
|
1190
|
-
Array built from the provided bodies
|
|
1191
|
-
"""
|
|
1192
|
-
from scipy.cluster.hierarchy import linkage
|
|
1193
|
-
nb_buoys = len(bodies)
|
|
1194
|
-
|
|
1195
|
-
if any(body.center_of_buoyancy is None for body in bodies):
|
|
1196
|
-
raise ValueError("The center of buoyancy of each body needs to be known for clustering")
|
|
1197
|
-
buoys_positions = np.stack([body.center_of_buoyancy for body in bodies])[:,:2]
|
|
1198
|
-
|
|
1199
|
-
ln_matrix = linkage(buoys_positions, method='centroid', metric='euclidean')
|
|
1200
|
-
|
|
1201
|
-
node_list = list(bodies) # list of nodes of the tree: the first nodes are single bodies
|
|
1202
|
-
|
|
1203
|
-
# Join the bodies, with an ordering consistent with the dendrogram.
|
|
1204
|
-
# Done by reading the linkage matrix: its i-th row contains the labels
|
|
1205
|
-
# of the two nodes that are merged to form the (n + i)-th node
|
|
1206
|
-
for ii in range(len(ln_matrix)):
|
|
1207
|
-
node_tag = ii + nb_buoys # the first nb_buoys tags are already taken
|
|
1208
|
-
merge_left = int(ln_matrix[ii,0])
|
|
1209
|
-
merge_right = int(ln_matrix[ii,1])
|
|
1210
|
-
# The new node is the parent of merge_left and merge_right
|
|
1211
|
-
new_node_ls = [node_list[merge_left], node_list[merge_right]]
|
|
1212
|
-
new_node = FloatingBody.join_bodies(*new_node_ls, name='node_{:d}'.format(node_tag))
|
|
1213
|
-
node_list.append(new_node)
|
|
1214
|
-
|
|
1215
|
-
# The last node is the parent of all others
|
|
1216
|
-
all_buoys = new_node
|
|
1217
|
-
|
|
1218
|
-
if name is not None:
|
|
1219
|
-
all_buoys.name = name
|
|
1220
|
-
|
|
1221
|
-
return all_buoys
|