capytaine 2.3.1__cp314-cp314-win_amd64.whl → 3.0.0a1__cp314-cp314-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.
Files changed (88) hide show
  1. capytaine/__about__.py +7 -2
  2. capytaine/__init__.py +11 -15
  3. capytaine/bem/engines.py +234 -354
  4. capytaine/bem/problems_and_results.py +14 -13
  5. capytaine/bem/solver.py +204 -80
  6. capytaine/bodies/bodies.py +278 -869
  7. capytaine/bodies/dofs.py +136 -9
  8. capytaine/bodies/hydrostatics.py +540 -0
  9. capytaine/bodies/multibodies.py +216 -0
  10. capytaine/green_functions/{libs/Delhommeau_float32.cp314-win_amd64.dll.a → Delhommeau_float32.cp314-win_amd64.dll.a} +0 -0
  11. capytaine/green_functions/Delhommeau_float32.cp314-win_amd64.pyd +0 -0
  12. capytaine/green_functions/{libs/Delhommeau_float64.cp314-win_amd64.dll.a → Delhommeau_float64.cp314-win_amd64.dll.a} +0 -0
  13. capytaine/green_functions/Delhommeau_float64.cp314-win_amd64.pyd +0 -0
  14. capytaine/green_functions/abstract_green_function.py +2 -2
  15. capytaine/green_functions/delhommeau.py +31 -16
  16. capytaine/green_functions/hams.py +19 -13
  17. capytaine/io/legacy.py +3 -103
  18. capytaine/io/xarray.py +11 -6
  19. capytaine/meshes/__init__.py +2 -6
  20. capytaine/meshes/abstract_meshes.py +375 -0
  21. capytaine/meshes/clean.py +302 -0
  22. capytaine/meshes/clip.py +347 -0
  23. capytaine/meshes/export.py +89 -0
  24. capytaine/meshes/geometry.py +244 -394
  25. capytaine/meshes/io.py +433 -0
  26. capytaine/meshes/meshes.py +617 -681
  27. capytaine/meshes/predefined/cylinders.py +22 -56
  28. capytaine/meshes/predefined/rectangles.py +26 -85
  29. capytaine/meshes/predefined/spheres.py +4 -11
  30. capytaine/meshes/quality.py +118 -407
  31. capytaine/meshes/surface_integrals.py +48 -29
  32. capytaine/meshes/symmetric_meshes.py +641 -0
  33. capytaine/meshes/visualization.py +353 -0
  34. capytaine/post_pro/free_surfaces.py +1 -4
  35. capytaine/post_pro/kochin.py +10 -10
  36. capytaine/tools/block_circulant_matrices.py +275 -0
  37. capytaine/tools/lists_of_points.py +2 -2
  38. capytaine/tools/memory_monitor.py +45 -0
  39. capytaine/tools/symbolic_multiplication.py +13 -1
  40. capytaine/tools/timer.py +58 -34
  41. capytaine-3.0.0a1.dist-info/DELVEWHEEL +2 -0
  42. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/METADATA +7 -2
  43. capytaine-3.0.0a1.dist-info/RECORD +70 -0
  44. capytaine/bodies/predefined/__init__.py +0 -6
  45. capytaine/bodies/predefined/cylinders.py +0 -151
  46. capytaine/bodies/predefined/rectangles.py +0 -111
  47. capytaine/bodies/predefined/spheres.py +0 -70
  48. capytaine/green_functions/FinGreen3D/.gitignore +0 -1
  49. capytaine/green_functions/FinGreen3D/FinGreen3D.f90 +0 -3589
  50. capytaine/green_functions/FinGreen3D/LICENSE +0 -165
  51. capytaine/green_functions/FinGreen3D/Makefile +0 -16
  52. capytaine/green_functions/FinGreen3D/README.md +0 -24
  53. capytaine/green_functions/FinGreen3D/test_program.f90 +0 -39
  54. capytaine/green_functions/LiangWuNoblesse/.gitignore +0 -1
  55. capytaine/green_functions/LiangWuNoblesse/LICENSE +0 -504
  56. capytaine/green_functions/LiangWuNoblesse/LiangWuNoblesseWaveTerm.f90 +0 -751
  57. capytaine/green_functions/LiangWuNoblesse/Makefile +0 -16
  58. capytaine/green_functions/LiangWuNoblesse/README.md +0 -2
  59. capytaine/green_functions/LiangWuNoblesse/test_program.f90 +0 -28
  60. capytaine/green_functions/libs/Delhommeau_float32.cp314-win_amd64.pyd +0 -0
  61. capytaine/green_functions/libs/Delhommeau_float64.cp314-win_amd64.pyd +0 -0
  62. capytaine/green_functions/libs/__init__.py +0 -0
  63. capytaine/io/mesh_loaders.py +0 -1086
  64. capytaine/io/mesh_writers.py +0 -692
  65. capytaine/io/meshio.py +0 -38
  66. capytaine/matrices/__init__.py +0 -16
  67. capytaine/matrices/block.py +0 -592
  68. capytaine/matrices/block_toeplitz.py +0 -325
  69. capytaine/matrices/builders.py +0 -89
  70. capytaine/matrices/linear_solvers.py +0 -232
  71. capytaine/matrices/low_rank.py +0 -395
  72. capytaine/meshes/clipper.py +0 -465
  73. capytaine/meshes/collections.py +0 -342
  74. capytaine/meshes/mesh_like_protocol.py +0 -37
  75. capytaine/meshes/properties.py +0 -276
  76. capytaine/meshes/quadratures.py +0 -80
  77. capytaine/meshes/symmetric.py +0 -462
  78. capytaine/tools/lru_cache.py +0 -49
  79. capytaine/ui/vtk/__init__.py +0 -3
  80. capytaine/ui/vtk/animation.py +0 -329
  81. capytaine/ui/vtk/body_viewer.py +0 -28
  82. capytaine/ui/vtk/helpers.py +0 -82
  83. capytaine/ui/vtk/mesh_viewer.py +0 -461
  84. capytaine-2.3.1.dist-info/DELVEWHEEL +0 -2
  85. capytaine-2.3.1.dist-info/RECORD +0 -97
  86. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/LICENSE +0 -0
  87. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/WHEEL +0 -0
  88. {capytaine-2.3.1.dist-info → capytaine-3.0.0a1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,540 @@
1
+ # Copyright (C) 2025 Capytaine developers
2
+ # See LICENSE file at <https://github.com/capytaine/capytaine>
3
+
4
+ import logging
5
+
6
+ import numpy as np
7
+ import xarray as xr
8
+ from abc import ABC
9
+
10
+ from capytaine.bodies.dofs import TranslationDof, RotationDof, is_rigid_body_dof
11
+
12
+ LOG = logging.getLogger(__name__)
13
+
14
+ class _HydrostaticsMixin(ABC):
15
+ # This class is not meant to be instantiated but only to be inherited by other classes to give them more methods.
16
+
17
+ @property
18
+ def wet_surface_area(self):
19
+ """Returns wet surface area."""
20
+ return self.mesh.wet_surface_area
21
+
22
+ @property
23
+ def volumes(self):
24
+ """Returns volumes using x, y, z components of the FloatingBody."""
25
+ return self.mesh.volumes
26
+
27
+ @property
28
+ def volume(self):
29
+ """Returns volume of the FloatingBody."""
30
+ return self.mesh.volume
31
+
32
+ @property
33
+ def disp_volume(self):
34
+ return self.mesh.disp_volume
35
+
36
+ def disp_mass(self, *, rho=1000.0):
37
+ return self.mesh.disp_mass(rho=rho)
38
+
39
+ @property
40
+ def center_of_buoyancy(self):
41
+ """Returns center of buoyancy of the FloatingBody."""
42
+ return self.mesh.center_of_buoyancy
43
+
44
+ @property
45
+ def waterplane_area(self):
46
+ """Returns water plane area of the FloatingBody."""
47
+ return self.mesh.waterplane_area
48
+
49
+ @property
50
+ def waterplane_center(self):
51
+ """Returns water plane center of the FloatingBody.
52
+
53
+ Note: Returns None if the FloatingBody is full submerged.
54
+ """
55
+ return self.mesh.waterplane_center
56
+
57
+ @property
58
+ def transversal_metacentric_radius(self):
59
+ """Returns transversal metacentric radius of the mesh."""
60
+ immersed_mesh = self.mesh.immersed_part()
61
+ inertia_moment = -immersed_mesh.waterplane_integral(immersed_mesh.faces_centers[:,1]**2)
62
+ return inertia_moment / self.disp_volume
63
+
64
+ @property
65
+ def longitudinal_metacentric_radius(self):
66
+ """Returns longitudinal metacentric radius of the mesh."""
67
+ immersed_mesh = self.mesh.immersed_part()
68
+ inertia_moment = -immersed_mesh.waterplane_integral(immersed_mesh.faces_centers[:,0]**2)
69
+ return inertia_moment / self.disp_volume
70
+
71
+ @property
72
+ def transversal_metacentric_height(self):
73
+ """Returns transversal metacentric height of the mesh."""
74
+ gb = self.center_of_mass - self.center_of_buoyancy
75
+ return self.transversal_metacentric_radius - gb[2]
76
+
77
+ @property
78
+ def longitudinal_metacentric_height(self):
79
+ """Returns longitudinal metacentric height of the mesh."""
80
+ gb = self.center_of_mass - self.center_of_buoyancy
81
+ return self.longitudinal_metacentric_radius - gb[2]
82
+
83
+ def dof_normals(self, dof):
84
+ """Returns dot product of the surface face normals and DOF"""
85
+ return np.sum(self.mesh.faces_normals * dof, axis=1)
86
+
87
+ def each_hydrostatic_stiffness(self, influenced_dof_name, radiating_dof_name, *,
88
+ influenced_dof_div=0.0, rho=1000.0, g=9.81):
89
+ r"""
90
+ Return the hydrostatic stiffness for a pair of DOFs.
91
+
92
+ :math:`C_{ij} = \rho g\iint_S (\hat{n} \cdot V_j) (w_i + z D_i) dS`
93
+
94
+ where :math:`\hat{n}` is surface normal,
95
+
96
+ :math:`V_i = u_i \hat{n}_x + v_i \hat{n}_y + w_i \hat{n}_z` is DOF vector and
97
+
98
+ :math:`D_i = \nabla \cdot V_i` is the divergence of the DOF.
99
+
100
+ Parameters
101
+ ----------
102
+ influenced_dof_name : str
103
+ Name of influenced DOF vector of the FloatingBody
104
+ radiating_dof_name: str
105
+ Name of radiating DOF vector of the FloatingBody
106
+ influenced_dof_div: np.ndarray (Face_count), optional
107
+ Influenced DOF divergence of the FloatingBody, by default 0.0.
108
+ rho: float, optional
109
+ water density, by default 1000.0
110
+ g: float, optional
111
+ Gravity acceleration, by default 9.81
112
+
113
+ Returns
114
+ -------
115
+ hs_ij: xarray.variable
116
+ hydrostatic_stiffness of ith DOF and jth DOF.
117
+
118
+ Note
119
+ ----
120
+ This function computes the hydrostatic stiffness assuming :math:`D_{i} = 0`.
121
+ If :math:`D_i \neq 0`, input the divergence interpolated to face centers.
122
+
123
+ General integral equations are used for the rigid body modes and
124
+ Neumann (1994) method is used for flexible modes.
125
+
126
+ References
127
+ ----------
128
+ Newman, John Nicholas. "Wave effects on deformable bodies."Applied ocean
129
+ research" 16.1 (1994): 47-59.
130
+ http://resolver.tudelft.nl/uuid:0adff84c-43c7-43aa-8cd8-d4c44240bed8
131
+
132
+ """
133
+ # Newman (1994) formula is not 'complete' as recovering the rigid body
134
+ # terms is not possible. https://doi.org/10.1115/1.3058702.
135
+
136
+ # Alternative is to use the general equation of hydrostatic and
137
+ # restoring coefficient for rigid modes and use Newman equation for elastic
138
+ # modes.
139
+
140
+ immersed_self = self.immersed_part()
141
+ immersed_mesh = immersed_self.mesh
142
+ influenced_dof = immersed_self.dofs[influenced_dof_name]
143
+ radiating_dof = immersed_self.dofs[radiating_dof_name]
144
+
145
+ if is_rigid_body_dof(influenced_dof) and is_rigid_body_dof(radiating_dof):
146
+ # Check that the directions of both dofs are canonical directions (1, 0, 0), (0, 1, 0) or (0, 0, 1)
147
+ for dof in (influenced_dof, radiating_dof):
148
+ if not hasattr(dof, "_standard_name"):
149
+ standard_dir = np.all(np.isclose(dof.direction, np.eye(3)), axis=1)
150
+ if not np.any(standard_dir):
151
+ raise NotImplementedError("Cannot evaluate hydrostatic stiffness for rigid body dof with non-standard directions. "
152
+ f"Got direction: {dof.direction}")
153
+ if isinstance(dof, TranslationDof) and np.where(standard_dir)[0] == 0:
154
+ dof._standard_name = "Surge"
155
+ elif isinstance(dof, TranslationDof) and np.where(standard_dir)[0] == 1:
156
+ dof._standard_name = "Sway"
157
+ elif isinstance(dof, TranslationDof) and np.where(standard_dir)[0] == 2:
158
+ dof._standard_name = "Heave"
159
+ elif isinstance(dof, RotationDof) and np.where(standard_dir)[0] == 0:
160
+ dof._standard_name = "Roll"
161
+ elif isinstance(dof, RotationDof) and np.where(standard_dir)[0] == 1:
162
+ dof._standard_name = "Pitch"
163
+ elif isinstance(dof, RotationDof) and np.where(standard_dir)[0] == 2:
164
+ dof._standard_name = "Yaw"
165
+
166
+ if self.center_of_mass is None:
167
+ raise ValueError(f"Trying to compute rigid-body hydrostatic stiffness for {self.name}, but no center of mass has been defined.\n"
168
+ f"Suggested solution: define a `center_of_mass` when initializing the FloatingBody {self.__short_str__()}.")
169
+ mass = self.disp_mass(rho=rho) if self.mass is None else self.mass
170
+
171
+ if isinstance(influenced_dof, RotationDof) or isinstance(radiating_dof, RotationDof):
172
+ if isinstance(influenced_dof, RotationDof) and isinstance(radiating_dof, RotationDof):
173
+ if not np.allclose(influenced_dof.rotation_center, radiating_dof.rotation_center):
174
+ raise NotImplementedError("Cannot evaluate hydrostatic stiffness when rotation dofs have different rotation center.")
175
+ if isinstance(influenced_dof, RotationDof):
176
+ xc, yc, zc = influenced_dof.rotation_center
177
+ else:
178
+ xc, yc, zc = radiating_dof.rotation_center
179
+
180
+ dof_pair = (influenced_dof._standard_name, radiating_dof._standard_name)
181
+ if dof_pair == ("Heave", "Heave"):
182
+ norm_hs_stiff = immersed_mesh.waterplane_area
183
+ elif dof_pair in [("Heave", "Roll"), ("Roll", "Heave")]:
184
+ norm_hs_stiff = -immersed_mesh.waterplane_integral(immersed_mesh.faces_centers[:,1] - yc)
185
+ elif dof_pair in [("Heave", "Pitch"), ("Pitch", "Heave")]:
186
+ norm_hs_stiff = immersed_mesh.waterplane_integral(immersed_mesh.faces_centers[:,0] - xc)
187
+ elif dof_pair == ("Roll", "Roll"):
188
+ norm_hs_stiff = (
189
+ -immersed_mesh.waterplane_integral((immersed_mesh.faces_centers[:,1] - yc)**2)
190
+ + immersed_mesh.volume*(immersed_mesh.center_of_buoyancy[2] - zc) - mass/rho*(self.center_of_mass[2] - zc)
191
+ )
192
+ elif dof_pair in [("Roll", "Pitch"), ("Pitch", "Roll")]:
193
+ norm_hs_stiff = immersed_mesh.waterplane_integral((immersed_mesh.faces_centers[:,0] - xc)
194
+ * (immersed_mesh.faces_centers[:,1] - yc))
195
+ elif dof_pair == ("Roll", "Yaw"):
196
+ norm_hs_stiff = - immersed_mesh.volume*(immersed_mesh.center_of_buoyancy[0] - xc) + mass/rho*(self.center_of_mass[0] - xc)
197
+ elif dof_pair == ("Pitch", "Pitch"):
198
+ norm_hs_stiff = (
199
+ -immersed_mesh.waterplane_integral((immersed_mesh.faces_centers[:,0] - xc)**2)
200
+ + immersed_mesh.volume*(immersed_mesh.center_of_buoyancy[2] - zc) - mass/rho*(self.center_of_mass[2] - zc)
201
+ )
202
+ elif dof_pair == ("Pitch", "Yaw"):
203
+ norm_hs_stiff = - immersed_mesh.volume*(immersed_mesh.center_of_buoyancy[1] - yc) + mass/rho*(self.center_of_mass[1] - yc)
204
+ else:
205
+ norm_hs_stiff = 0.0
206
+
207
+ else:
208
+ if self.mass is not None and not np.isclose(self.mass, self.disp_mass(rho=rho), rtol=1e-4):
209
+ raise NotImplementedError(
210
+ f"Trying to compute the hydrostatic stiffness for dofs {radiating_dof_name} and {influenced_dof_name}"
211
+ f"of body {self.name}, which is not neutrally buoyant (mass={self.mass}, disp_mass={self.disp_mass(rho=rho)}).\n"
212
+ f"This case has not been implemented in Capytaine. You need either a single rigid body or a neutrally buoyant body."
213
+ )
214
+
215
+ if np.any(self.mesh.faces_centers[:, 2] > 1e-2) and np.any(influenced_dof_div != 0.0):
216
+ raise NotImplementedError(
217
+ "When computing hydrostatics of flexible dofs while providing the divergence of the dof, please make sure the mesh is clipped beforehand and provide the divergence only on the immersed faces of the clipped mesh."
218
+ )
219
+
220
+ # Newman (1994) formula for flexible DOFs
221
+ influenced_dof = np.array(immersed_self.dofs[influenced_dof_name])
222
+ radiating_dof = np.array(immersed_self.dofs[radiating_dof_name])
223
+ influenced_dof_div_array = np.array(influenced_dof_div)
224
+
225
+ radiating_dof_normal = immersed_self.dof_normals(radiating_dof)
226
+ z_influenced_dof_div = influenced_dof[:,2] + immersed_self.mesh.faces_centers[:,2] * influenced_dof_div_array
227
+ norm_hs_stiff = immersed_self.mesh.surface_integral( -radiating_dof_normal * z_influenced_dof_div)
228
+
229
+ hs_stiff = rho * g * norm_hs_stiff
230
+
231
+ return xr.DataArray([[hs_stiff]],
232
+ dims=['influenced_dof', 'radiating_dof'],
233
+ coords={'influenced_dof': [influenced_dof_name],
234
+ 'radiating_dof': [radiating_dof_name]},
235
+ name="hydrostatic_stiffness"
236
+ )
237
+
238
+ def compute_hydrostatic_stiffness(self, *, divergence=None, rho=1000.0, g=9.81):
239
+ r"""
240
+ Compute hydrostatic stiffness matrix for all DOFs of the body.
241
+
242
+ :math:`C_{ij} = \rho g\iint_S (\hat{n} \cdot V_j) (w_i + z D_i) dS`
243
+
244
+ where :math:`\hat{n}` is surface normal,
245
+
246
+ :math:`V_i = u_i \hat{n}_x + v_i \hat{n}_y + w_i \hat{n}_z` is DOF vector and
247
+
248
+ :math:`D_i = \nabla \cdot V_i` is the divergence of the DOF.
249
+
250
+ Parameters
251
+ ----------
252
+ divergence : dict mapping a dof name to an array of shape (nb_faces) or
253
+ xarray.DataArray of shape (nb_dofs × nb_faces), optional
254
+ Divergence of the DOFs, by default None
255
+ rho : float, optional
256
+ Water density, by default 1000.0
257
+ g: float, optional
258
+ Gravity acceleration, by default 9.81
259
+
260
+ Returns
261
+ -------
262
+ xr.DataArray
263
+ Matrix of hydrostatic stiffness
264
+
265
+ Note
266
+ ----
267
+ This function computes the hydrostatic stiffness assuming :math:`D_{i} = 0`.
268
+ If :math:`D_i \neq 0`, input the divergence interpolated to face centers.
269
+
270
+ General integral equations are used for the rigid body modes and
271
+ Neumann (1994) method is used for flexible modes.
272
+
273
+ References
274
+ ----------
275
+ Newman, John Nicholas. "Wave effects on deformable bodies."Applied ocean
276
+ research" 16.1 (1994): 47-59.
277
+ http://resolver.tudelft.nl/uuid:0adff84c-43c7-43aa-8cd8-d4c44240bed8
278
+
279
+ """
280
+ if len(self.dofs) == 0:
281
+ raise AttributeError("Cannot compute hydrostatics stiffness on {} since no dof has been defined.".format(self.name))
282
+
283
+ def divergence_dof(influenced_dof):
284
+ if is_rigid_body_dof(influenced_dof):
285
+ return 0.0 # Dummy value that is not actually used afterwards.
286
+ elif divergence is None:
287
+ return 0.0
288
+ elif isinstance(divergence, dict) and influenced_dof in divergence.keys():
289
+ return divergence[influenced_dof]
290
+ elif isinstance(divergence, xr.DataArray) and influenced_dof in divergence.coords["influenced_dof"]:
291
+ return divergence.sel(influenced_dof=influenced_dof).values
292
+ else:
293
+ LOG.warning("Computing hydrostatic stiffness without the divergence of {}".format(influenced_dof))
294
+ return 0.0
295
+
296
+ hs_set = xr.merge([
297
+ self.each_hydrostatic_stiffness(
298
+ influenced_dof_name, radiating_dof_name,
299
+ influenced_dof_div = divergence_dof(influenced_dof_name),
300
+ rho=rho, g=g
301
+ )
302
+ for radiating_dof_name in self.dofs
303
+ for influenced_dof_name in self.dofs
304
+ ], compat='no_conflicts', join="outer")
305
+
306
+ # Reorder dofs
307
+ K = hs_set.hydrostatic_stiffness.sel(influenced_dof=list(self.dofs.keys()), radiating_dof=list(self.dofs.keys()))
308
+ return K
309
+
310
+ def compute_rigid_body_inertia(self, *, rho=1000.0, output_type="body_dofs"):
311
+ """
312
+ Inertia Mass matrix of the body for 6 rigid DOFs.
313
+
314
+ Parameters
315
+ ----------
316
+ rho : float, optional
317
+ Density of water, by default 1000.0
318
+ output_type : {"body_dofs", "rigid_dofs", "all_dofs"}
319
+ Type of DOFs for mass mat output, by default "body_dofs".
320
+
321
+ Returns
322
+ -------
323
+ xarray.DataArray
324
+ Inertia matrix
325
+
326
+ Raises
327
+ ------
328
+ ValueError
329
+ If output_type is not in {"body_dofs", "rigid_dofs", "all_dofs"}.
330
+ """
331
+ if len(self.dofs) == 0:
332
+ return xr.DataArray(np.nan * np.zeros([0, 0]),
333
+ dims=['influenced_dof', 'radiating_dof'],
334
+ coords={'influenced_dof': [],
335
+ 'radiating_dof': []},
336
+ name="inertia_matrix")
337
+
338
+ if self.center_of_mass is None:
339
+ raise ValueError(f"Trying to compute rigid-body inertia matrix for {self.name}, but no center of mass has been defined.\n"
340
+ f"Suggested solution: define a `center_of_mass` attribute for the FloatingBody {self.name}.")
341
+
342
+ rotation_dofs = [dof for dof in self.dofs.values() if isinstance(dof, RotationDof)]
343
+ if len(rotation_dofs) == 0:
344
+ rc = np.array([np.nan, np.nan, np.nan]) # Dummy placeholder
345
+ if output_type != "body_dofs":
346
+ raise ValueError("Cannot compute rigid body inertia of a body without any rotation dofs, since the rotation center cannot be defined.")
347
+ elif len(rotation_dofs) == 1:
348
+ rc = rotation_dofs[0].rotation_center
349
+ else:
350
+ rcs = [dof.rotation_center for dof in self.dofs.values() if isinstance(dof, RotationDof)]
351
+ if not np.all(np.all(np.isclose(rcs[0], rcs[1:]), axis=1)):
352
+ raise NotImplementedError(f"Trying to compute rigid-body inertia matrix for {self.name}, but the rotations dofs have different rotation centers.")
353
+ rc = rcs[0]
354
+
355
+ for dof in self.dofs.values():
356
+ if is_rigid_body_dof(dof) and not hasattr(dof, "_standard_name"):
357
+ standard_dir = np.all(np.isclose(dof.direction, np.eye(3)), axis=1)
358
+ if not np.any(standard_dir):
359
+ raise NotImplementedError("Cannot evaluate hydrostatic stiffness for rigid body dof with non-standard directions. "
360
+ f"Got direction: {dof.direction}")
361
+ if isinstance(dof, TranslationDof) and np.where(standard_dir)[0] == 0:
362
+ dof._standard_name = "Surge"
363
+ elif isinstance(dof, TranslationDof) and np.where(standard_dir)[0] == 1:
364
+ dof._standard_name = "Sway"
365
+ elif isinstance(dof, TranslationDof) and np.where(standard_dir)[0] == 2:
366
+ dof._standard_name = "Heave"
367
+ elif isinstance(dof, RotationDof) and np.where(standard_dir)[0] == 0:
368
+ dof._standard_name = "Roll"
369
+ elif isinstance(dof, RotationDof) and np.where(standard_dir)[0] == 1:
370
+ dof._standard_name = "Pitch"
371
+ elif isinstance(dof, RotationDof) and np.where(standard_dir)[0] == 2:
372
+ dof._standard_name = "Yaw"
373
+
374
+ fcs = (self.mesh.faces_centers - rc).T
375
+ combinations = np.array([fcs[0]**2, fcs[1]**2, fcs[2]**2, fcs[0]*fcs[1],
376
+ fcs[1]*fcs[2], fcs[2]*fcs[0]])
377
+ integrals = np.array([
378
+ [np.sum(normal_i * fcs[axis] * combination * self.mesh.faces_areas)
379
+ for combination in combinations]
380
+ for axis, normal_i in enumerate(self.mesh.faces_normals.T)])
381
+
382
+ inertias = 1/self.volume * np.array([
383
+ (integrals[0,1] + integrals[0,2] + integrals[1,1]/3
384
+ + integrals[1,2] + integrals[2,1] + integrals[2,2]/3)/3,
385
+ (integrals[0,0]/3 + integrals[0,2] + integrals[1,0]
386
+ + integrals[1,2] + integrals[2,0] + integrals[2,2]/3)/3,
387
+ (integrals[0,0]/3 + integrals[0,1] + integrals[1,0]
388
+ + integrals[1,1]/3 + integrals[2,0] + integrals[2,1] )/3,
389
+ integrals[2,3],
390
+ integrals[0,4],
391
+ integrals[1,5]
392
+ ])
393
+
394
+ cog = self.center_of_mass - rc
395
+ volumic_inertia_matrix = self.mesh.disp_volume * np.array([
396
+ [1.0, 0, 0, 0, cog[2], -cog[1] ],
397
+ [0, 1.0, 0, -cog[2], 0, cog[0] ],
398
+ [0, 0, 1.0, cog[1], -cog[0], 0 ],
399
+ [0, -cog[2], cog[1], inertias[0], -inertias[3], -inertias[5]],
400
+ [cog[2], 0, -cog[0], -inertias[3], inertias[1], -inertias[4]],
401
+ [-cog[1], cog[0], 0, -inertias[5], -inertias[4], inertias[2] ],
402
+ ])
403
+
404
+ density = rho if self.mass is None else self.mass/self.volume
405
+ inertia_matrix = density * volumic_inertia_matrix
406
+
407
+ standard_rigid_dof_names = ["Surge", "Sway", "Heave", "Roll", "Pitch", "Yaw"]
408
+ rigid_inertia_matrix_xr = xr.DataArray(data=np.asarray(inertia_matrix),
409
+ dims=['influenced_dof', 'radiating_dof'],
410
+ coords={'influenced_dof': standard_rigid_dof_names,
411
+ 'radiating_dof': standard_rigid_dof_names,
412
+ },
413
+ name="inertia_matrix")
414
+
415
+ rigid_dofs = {name: dof for name, dof in self.dofs.items() if is_rigid_body_dof(dof)}
416
+ rigid_dof_names = list(rigid_dofs.keys())
417
+ if len(rigid_dof_names) > 0:
418
+ data = [
419
+ [
420
+ rigid_inertia_matrix_xr.sel(
421
+ influenced_dof=influenced_dof._standard_name,
422
+ radiating_dof=radiating_dof._standard_name
423
+ ).values
424
+ for radiating_dof in rigid_dofs.values()
425
+ ]
426
+ for influenced_dof in rigid_dofs.values()
427
+ ]
428
+ else:
429
+ data = np.empty((0, 0))
430
+ rigid_inertia_matrix_xr = xr.DataArray(
431
+ data=data,
432
+ dims=['influenced_dof', 'radiating_dof'],
433
+ coords={'influenced_dof': rigid_dof_names,
434
+ 'radiating_dof': rigid_dof_names},
435
+ name="inertia_matrix"
436
+ )
437
+
438
+ # Body DOFs (Default as np.nan)
439
+ body_dof_names = list(self.dofs)
440
+ body_dof_count = len(self.dofs)
441
+ other_dofs_inertia_matrix_xr = xr.DataArray(np.nan * np.zeros([body_dof_count, body_dof_count]),
442
+ dims=['influenced_dof', 'radiating_dof'],
443
+ coords={'influenced_dof': body_dof_names,
444
+ 'radiating_dof': body_dof_names},
445
+ name="inertia_matrix")
446
+
447
+ total_mass_xr = xr.merge([rigid_inertia_matrix_xr, other_dofs_inertia_matrix_xr], compat="override", join="outer").inertia_matrix
448
+
449
+ non_rigid_dofs = set(body_dof_names) - set(rigid_dof_names)
450
+
451
+ if output_type == "body_dofs":
452
+ if len(non_rigid_dofs) > 0:
453
+ LOG.warning(f"Non-rigid dofs {non_rigid_dofs} detected: their \
454
+ inertia coefficients are assigned as NaN.")
455
+
456
+ inertia_matrix_xr = total_mass_xr.sel(influenced_dof=body_dof_names,
457
+ radiating_dof=body_dof_names)
458
+ elif output_type == "rigid_dofs":
459
+ inertia_matrix_xr = total_mass_xr.sel(influenced_dof=rigid_dof_names,
460
+ radiating_dof=rigid_dof_names)
461
+ elif output_type == "all_dofs":
462
+ if len(non_rigid_dofs) > 0:
463
+ LOG.warning("Non-rigid dofs: {non_rigid_dofs} are detected and \
464
+ respective inertia coefficients are assigned as NaN.")
465
+
466
+ inertia_matrix_xr = total_mass_xr
467
+ else:
468
+ raise ValueError(f"output_type should be either 'body_dofs', \
469
+ 'all_dofs' or 'rigid_dofs'. Given output_type = '{output_type}'.")
470
+
471
+ return inertia_matrix_xr
472
+
473
+
474
+ def compute_hydrostatics(self, *, rho=1000.0, g=9.81, divergence=None):
475
+ """Compute hydrostatics of the FloatingBody.
476
+
477
+ Parameters
478
+ ----------
479
+ rho : float, optional
480
+ Density of Water. The default is 1000.
481
+ g: float, optional
482
+ Gravity acceleration. The default is 9.81.
483
+ divergence : np.ndarray, optional
484
+ Divergence of the DOFs.
485
+
486
+ Returns
487
+ -------
488
+ hydrostatics : dict
489
+ All hydrostatics values of the FloatingBody.
490
+ """
491
+ if self.center_of_mass is None:
492
+ raise ValueError(f"Trying to compute hydrostatics for {self.name}, but no center of mass has been defined.\n"
493
+ f"Suggested solution: define a `center_of_mass` attribute for the FloatingBody {self.name}.")
494
+
495
+ full_mesh_vertices = self.mesh.vertices
496
+ coord_max = full_mesh_vertices.max(axis=0)
497
+ coord_min = full_mesh_vertices.min(axis=0)
498
+ full_length, full_breadth, depth = full_mesh_vertices.max(axis=0) - full_mesh_vertices.min(axis=0)
499
+
500
+ vertices = self.mesh.immersed_part().vertices
501
+ sub_length, sub_breadth, _ = vertices.max(axis=0) - vertices.min(axis=0)
502
+
503
+ if abs(self.waterplane_area) > 1e-10:
504
+ water_plane_idx = np.isclose(vertices[:,2], 0.0)
505
+ water_plane = vertices[water_plane_idx][:,:-1]
506
+ wl_length, wl_breadth = water_plane.max(axis=0) - water_plane.min(axis=0)
507
+ else:
508
+ wl_length, wl_breadth = 0.0, 0.0
509
+
510
+ hydrostatics = {}
511
+ hydrostatics["g"] = g
512
+ hydrostatics["rho"] = rho
513
+ hydrostatics["center_of_mass"] = self.center_of_mass
514
+
515
+ hydrostatics["wet_surface_area"] = self.wet_surface_area
516
+ hydrostatics["disp_volume"] = self.disp_volume
517
+ hydrostatics["disp_mass"] = self.disp_mass(rho=rho)
518
+ hydrostatics["center_of_buoyancy"] = self.center_of_buoyancy
519
+ hydrostatics["waterplane_center"] = np.append(self.waterplane_center, 0.0)
520
+ hydrostatics["waterplane_area"] = self.waterplane_area
521
+ hydrostatics["transversal_metacentric_radius"] = self.transversal_metacentric_radius
522
+ hydrostatics["longitudinal_metacentric_radius"] = self.longitudinal_metacentric_radius
523
+ hydrostatics["transversal_metacentric_height"] = self.transversal_metacentric_height
524
+ hydrostatics["longitudinal_metacentric_height"] = self.longitudinal_metacentric_height
525
+ self.hydrostatic_stiffness = hydrostatics["hydrostatic_stiffness"] = self.compute_hydrostatic_stiffness(
526
+ divergence=divergence, rho=rho, g=g)
527
+
528
+ hydrostatics["length_overall"] = full_length
529
+ hydrostatics["breadth_overall"] = full_breadth
530
+ hydrostatics["depth"] = depth
531
+ hydrostatics["draught"] = np.abs(coord_min[2])
532
+ hydrostatics["length_at_waterline"] = wl_length
533
+ hydrostatics["breadth_at_waterline"] = wl_breadth
534
+ hydrostatics["length_overall_submerged"] = sub_length
535
+ hydrostatics["breadth_overall_submerged"] = sub_breadth
536
+ if any(dof.lower() in {"surge", "sway", "heave", "roll", "pitch", "yaw"}
537
+ for dof in self.dofs) > 0: # If there is at least one rigid body dof:
538
+ self.inertia_matrix = hydrostatics["inertia_matrix"] = self.compute_rigid_body_inertia(rho=rho)
539
+
540
+ return hydrostatics