capytaine 2.0__cp38-cp38-win_amd64.whl → 2.2__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 +3 -3
- capytaine/__init__.py +16 -14
- capytaine/bem/airy_waves.py +4 -6
- capytaine/bem/engines.py +146 -25
- capytaine/bem/problems_and_results.py +217 -106
- capytaine/bem/solver.py +179 -47
- capytaine/bodies/__init__.py +1 -1
- capytaine/bodies/bodies.py +207 -39
- capytaine/bodies/predefined/__init__.py +0 -2
- capytaine/bodies/predefined/cylinders.py +0 -2
- capytaine/bodies/predefined/rectangles.py +0 -2
- capytaine/bodies/predefined/spheres.py +0 -2
- capytaine/green_functions/abstract_green_function.py +0 -3
- capytaine/green_functions/delhommeau.py +225 -63
- capytaine/green_functions/libs/Delhommeau_float32.cp38-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/Delhommeau_float32.cp38-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp38-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/Delhommeau_float64.cp38-win_amd64.pyd +0 -0
- capytaine/io/bemio.py +17 -16
- capytaine/io/legacy.py +52 -20
- capytaine/io/mesh_loaders.py +49 -27
- capytaine/io/mesh_writers.py +1 -3
- capytaine/io/meshio.py +4 -1
- capytaine/io/xarray.py +73 -35
- capytaine/matrices/__init__.py +0 -2
- capytaine/matrices/block.py +23 -2
- capytaine/matrices/block_toeplitz.py +0 -2
- capytaine/matrices/builders.py +2 -4
- capytaine/matrices/linear_solvers.py +84 -7
- capytaine/matrices/low_rank.py +0 -2
- capytaine/meshes/__init__.py +0 -2
- capytaine/meshes/clipper.py +0 -3
- capytaine/meshes/collections.py +49 -20
- capytaine/meshes/geometry.py +3 -6
- capytaine/meshes/meshes.py +170 -81
- capytaine/meshes/predefined/__init__.py +0 -1
- capytaine/meshes/predefined/cylinders.py +48 -7
- capytaine/meshes/predefined/rectangles.py +43 -10
- capytaine/meshes/predefined/spheres.py +15 -4
- capytaine/meshes/properties.py +43 -2
- capytaine/meshes/quadratures.py +80 -0
- capytaine/meshes/quality.py +1 -3
- capytaine/meshes/surface_integrals.py +0 -1
- capytaine/meshes/symmetric.py +55 -14
- capytaine/post_pro/free_surfaces.py +4 -7
- capytaine/post_pro/impedance.py +12 -10
- capytaine/post_pro/kochin.py +5 -3
- capytaine/post_pro/rao.py +16 -22
- capytaine/tools/cache_on_disk.py +26 -0
- capytaine/tools/deprecation_handling.py +2 -2
- capytaine/tools/lists_of_points.py +13 -3
- capytaine/tools/lru_cache.py +23 -29
- capytaine/tools/optional_imports.py +0 -2
- capytaine/tools/prony_decomposition.py +0 -3
- capytaine/tools/symbolic_multiplication.py +107 -0
- capytaine/ui/cli.py +7 -27
- capytaine/ui/rich.py +5 -0
- capytaine/ui/vtk/__init__.py +0 -3
- capytaine/ui/vtk/animation.py +28 -8
- capytaine/ui/vtk/body_viewer.py +2 -2
- capytaine/ui/vtk/helpers.py +0 -3
- capytaine/ui/vtk/mesh_viewer.py +0 -3
- capytaine-2.2.dist-info/DELVEWHEEL +2 -0
- {capytaine-2.0.dist-info → capytaine-2.2.dist-info}/METADATA +32 -14
- capytaine-2.2.dist-info/RECORD +82 -0
- capytaine.libs/{.load-order-capytaine-2.0 → .load-order-capytaine-2.2} +2 -1
- capytaine.libs/libgcc_s_seh-1.dll +0 -0
- capytaine.libs/libgfortran-5.dll +0 -0
- capytaine.libs/libgomp-1.dll +0 -0
- capytaine.libs/libquadmath-0.dll +0 -0
- capytaine.libs/libwinpthread-1.dll +0 -0
- capytaine/green_functions/libDelhommeau/.gitignore +0 -5
- capytaine/green_functions/libDelhommeau/LICENSE +0 -203
- capytaine/green_functions/libDelhommeau/Makefile +0 -123
- capytaine/green_functions/libDelhommeau/README.md +0 -15
- capytaine/green_functions/libDelhommeau/benchmarks/openmp/benchmark_omp.f90 +0 -212
- capytaine/green_functions/libDelhommeau/benchmarks/openmp/display_mesh.py +0 -7
- capytaine/green_functions/libDelhommeau/benchmarks/openmp/read_output.py +0 -82
- capytaine/green_functions/libDelhommeau/benchmarks/profiling/benchmark_profiling.f90 +0 -201
- capytaine/green_functions/libDelhommeau/benchmarks/tabulations/benchmark_tabulation.f90 +0 -87
- capytaine/green_functions/libDelhommeau/examples/minimal/minimal_example.f90 +0 -213
- capytaine/green_functions/libDelhommeau/examples/minimal/minimal_example.py +0 -60
- capytaine/green_functions/libDelhommeau/src/Delhommeau_integrals.f90 +0 -311
- capytaine/green_functions/libDelhommeau/src/Green_Rankine.f90 +0 -148
- capytaine/green_functions/libDelhommeau/src/Green_wave.f90 +0 -303
- capytaine/green_functions/libDelhommeau/src/constants.f90 +0 -16
- capytaine/green_functions/libDelhommeau/src/float32.f90 +0 -7
- capytaine/green_functions/libDelhommeau/src/float64.f90 +0 -7
- capytaine/green_functions/libDelhommeau/src/matrices.f90 +0 -274
- capytaine/green_functions/libDelhommeau/src/old_Prony_decomposition.f90 +0 -636
- capytaine/green_functions/libs/XieDelhommeau_float32.cp38-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/XieDelhommeau_float32.cp38-win_amd64.pyd +0 -0
- capytaine/green_functions/libs/XieDelhommeau_float64.cp38-win_amd64.dll.a +0 -0
- capytaine/green_functions/libs/XieDelhommeau_float64.cp38-win_amd64.pyd +0 -0
- capytaine-2.0.dist-info/DELVEWHEEL +0 -2
- capytaine-2.0.dist-info/RECORD +0 -100
- {capytaine-2.0.dist-info → capytaine-2.2.dist-info}/LICENSE +0 -0
- {capytaine-2.0.dist-info → capytaine-2.2.dist-info}/WHEEL +0 -0
- {capytaine-2.0.dist-info → capytaine-2.2.dist-info}/entry_points.txt +0 -0
capytaine/bodies/bodies.py
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# coding: utf-8
|
|
3
1
|
"""Floating bodies to be used in radiation-diffraction problems."""
|
|
4
|
-
# Copyright (C) 2017-
|
|
5
|
-
# See LICENSE file at <https://github.com/
|
|
2
|
+
# Copyright (C) 2017-2024 Matthieu Ancellin
|
|
3
|
+
# See LICENSE file at <https://github.com/capytaine/capytaine>
|
|
6
4
|
|
|
7
5
|
import logging
|
|
8
6
|
import copy
|
|
9
|
-
from itertools import chain, accumulate,
|
|
7
|
+
from itertools import chain, accumulate, zip_longest
|
|
8
|
+
from functools import cached_property
|
|
10
9
|
|
|
11
10
|
import numpy as np
|
|
12
11
|
import xarray as xr
|
|
13
12
|
|
|
14
|
-
from capytaine.
|
|
15
|
-
meshio = silently_import_optional_dependency("meshio")
|
|
16
|
-
|
|
13
|
+
from capytaine.meshes.collections import CollectionOfMeshes
|
|
17
14
|
from capytaine.meshes.geometry import Abstract3DObject, ClippableMixin, Plane, inplace_transformation
|
|
15
|
+
from capytaine.meshes.properties import connected_components, connected_components_of_waterline
|
|
18
16
|
from capytaine.meshes.meshes import Mesh
|
|
19
17
|
from capytaine.meshes.symmetric import build_regular_array_of_meshes
|
|
20
|
-
from capytaine.meshes.collections import CollectionOfMeshes
|
|
21
18
|
from capytaine.bodies.dofs import RigidBodyDofsPlaceholder
|
|
22
19
|
|
|
20
|
+
from capytaine.tools.optional_imports import silently_import_optional_dependency
|
|
21
|
+
meshio = silently_import_optional_dependency("meshio")
|
|
22
|
+
|
|
23
23
|
LOG = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
25
|
TRANSLATION_DOFS_DIRECTIONS = {"surge": (1, 0, 0), "sway": (0, 1, 0), "heave": (0, 0, 1)}
|
|
@@ -40,8 +40,12 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
40
40
|
Parameters
|
|
41
41
|
----------
|
|
42
42
|
mesh : Mesh or CollectionOfMeshes, optional
|
|
43
|
-
the mesh describing the geometry of the floating body.
|
|
43
|
+
the mesh describing the geometry of the hull of the floating body.
|
|
44
44
|
If none is given, a empty one is created.
|
|
45
|
+
lid_mesh : Mesh or CollectionOfMeshes or None, optional
|
|
46
|
+
a mesh of an internal lid for irregular frequencies removal.
|
|
47
|
+
Unlike the mesh of the hull, no dof is defined on the lid_mesh.
|
|
48
|
+
If none is given, none is used when solving the Boundary Integral Equation.
|
|
45
49
|
dofs : dict, optional
|
|
46
50
|
the degrees of freedom of the body.
|
|
47
51
|
If none is given, a empty dictionary is initialized.
|
|
@@ -57,7 +61,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
57
61
|
If none is given, the one of the mesh is used.
|
|
58
62
|
"""
|
|
59
63
|
|
|
60
|
-
def __init__(self, mesh=None, dofs=None, mass=None, center_of_mass=None, name=None):
|
|
64
|
+
def __init__(self, mesh=None, dofs=None, mass=None, center_of_mass=None, name=None, *, lid_mesh=None):
|
|
61
65
|
if mesh is None:
|
|
62
66
|
self.mesh = Mesh(name="dummy_mesh")
|
|
63
67
|
|
|
@@ -71,6 +75,11 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
71
75
|
else:
|
|
72
76
|
raise TypeError("Unrecognized `mesh` object passed to the FloatingBody constructor.")
|
|
73
77
|
|
|
78
|
+
if lid_mesh is not None:
|
|
79
|
+
self.lid_mesh = lid_mesh.with_normal_vector_going_down(inplace=False)
|
|
80
|
+
else:
|
|
81
|
+
self.lid_mesh = None
|
|
82
|
+
|
|
74
83
|
if name is None and mesh is None:
|
|
75
84
|
self.name = "dummy_body"
|
|
76
85
|
elif name is None:
|
|
@@ -84,6 +93,9 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
84
93
|
else:
|
|
85
94
|
self.center_of_mass = None
|
|
86
95
|
|
|
96
|
+
if self.mesh.nb_vertices > 0 and self.mesh.nb_faces > 0:
|
|
97
|
+
self.mesh.heal_mesh()
|
|
98
|
+
|
|
87
99
|
if dofs is None:
|
|
88
100
|
self.dofs = {}
|
|
89
101
|
elif isinstance(dofs, RigidBodyDofsPlaceholder):
|
|
@@ -94,21 +106,19 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
94
106
|
else:
|
|
95
107
|
self.dofs = dofs
|
|
96
108
|
|
|
97
|
-
|
|
98
|
-
LOG.warning(f"New floating body (with empty mesh!): {self.name}.")
|
|
99
|
-
else:
|
|
100
|
-
self.mesh.heal_mesh()
|
|
101
|
-
LOG.info(f"New floating body: {self.name}.")
|
|
109
|
+
LOG.info(f"New floating body: {self.__str__()}.")
|
|
102
110
|
|
|
103
111
|
@staticmethod
|
|
104
112
|
def from_meshio(mesh, name=None) -> 'FloatingBody':
|
|
105
|
-
"""Create a FloatingBody from a meshio mesh object.
|
|
113
|
+
"""Create a FloatingBody from a meshio mesh object.
|
|
114
|
+
Kinda deprecated, use cpt.load_mesh instead."""
|
|
106
115
|
from capytaine.io.meshio import load_from_meshio
|
|
107
116
|
return FloatingBody(mesh=load_from_meshio(mesh, name), name=name)
|
|
108
117
|
|
|
109
118
|
@staticmethod
|
|
110
119
|
def from_file(filename: str, file_format=None, name=None) -> 'FloatingBody':
|
|
111
|
-
"""Create a FloatingBody from a mesh file using meshmagick.
|
|
120
|
+
"""Create a FloatingBody from a mesh file using meshmagick.
|
|
121
|
+
Kinda deprecated, use cpt.load_mesh instead."""
|
|
112
122
|
from capytaine.io.mesh_loaders import load_mesh
|
|
113
123
|
if name is None: name = filename
|
|
114
124
|
mesh = load_mesh(filename, file_format, name=f"{name}_mesh")
|
|
@@ -118,6 +128,13 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
118
128
|
"""Arbitrary order. The point is to sort together the problems involving the same body."""
|
|
119
129
|
return self.name < other.name
|
|
120
130
|
|
|
131
|
+
@cached_property
|
|
132
|
+
def mesh_including_lid(self):
|
|
133
|
+
if self.lid_mesh is not None:
|
|
134
|
+
return CollectionOfMeshes([self.mesh, self.lid_mesh])
|
|
135
|
+
else:
|
|
136
|
+
return self.mesh
|
|
137
|
+
|
|
121
138
|
##########
|
|
122
139
|
# Dofs #
|
|
123
140
|
##########
|
|
@@ -176,7 +193,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
176
193
|
if hasattr(self, point_attr) and getattr(self, point_attr) is not None:
|
|
177
194
|
axis_point = getattr(self, point_attr)
|
|
178
195
|
LOG.info(f"The rotation dof {name} has been initialized around the point: "
|
|
179
|
-
f"{self.
|
|
196
|
+
f"{self.__short_str__()}.{point_attr} = {getattr(self, point_attr)}")
|
|
180
197
|
break
|
|
181
198
|
else:
|
|
182
199
|
axis_point = np.array([0, 0, 0])
|
|
@@ -270,7 +287,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
270
287
|
"""Returns volume of the FloatingBody."""
|
|
271
288
|
return self.mesh.volume
|
|
272
289
|
|
|
273
|
-
def disp_mass(self, *, rho=1000):
|
|
290
|
+
def disp_mass(self, *, rho=1000.0):
|
|
274
291
|
return self.mesh.disp_mass(rho=rho)
|
|
275
292
|
|
|
276
293
|
@property
|
|
@@ -351,7 +368,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
351
368
|
except Exception as e:
|
|
352
369
|
raise ValueError(
|
|
353
370
|
f"Failed to infer the rotation center of {self.name} to compute rigid body hydrostatics.\n"
|
|
354
|
-
f"Possible fix: add a `rotation_center`
|
|
371
|
+
f"Possible fix: add a `rotation_center` attribute to {self.name}.\n"
|
|
355
372
|
"Note that rigid body hydrostatic methods currently assume that the three rotation dofs have the same rotation center."
|
|
356
373
|
) from e
|
|
357
374
|
|
|
@@ -444,10 +461,10 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
444
461
|
else:
|
|
445
462
|
norm_hs_stiff = 0.0
|
|
446
463
|
else:
|
|
447
|
-
if self.mass is not None and np.isclose(self.mass, self.disp_mass(rho), rtol=1e-4):
|
|
464
|
+
if self.mass is not None and not np.isclose(self.mass, self.disp_mass(rho=rho), rtol=1e-4):
|
|
448
465
|
raise NotImplementedError(
|
|
449
466
|
f"Trying to compute the hydrostatic stiffness for dofs {radiating_dof_name} and {influenced_dof_name}"
|
|
450
|
-
f"of body {self.name}, which is not neutrally buoyant (mass={
|
|
467
|
+
f"of body {self.name}, which is not neutrally buoyant (mass={self.mass}, disp_mass={self.disp_mass(rho=rho)}).\n"
|
|
451
468
|
f"This case has not been implemented in Capytaine. You need either a single rigid body or a neutrally buoyant body."
|
|
452
469
|
)
|
|
453
470
|
|
|
@@ -515,7 +532,9 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
515
532
|
raise AttributeError("Cannot compute hydrostatics stiffness on {} since no dof has been defined.".format(self.name))
|
|
516
533
|
|
|
517
534
|
def divergence_dof(influenced_dof):
|
|
518
|
-
if
|
|
535
|
+
if influenced_dof.lower() in [*TRANSLATION_DOFS_DIRECTIONS, *ROTATION_DOFS_AXIS]:
|
|
536
|
+
return 0.0 # Dummy value that is not actually used afterwards.
|
|
537
|
+
elif divergence is None:
|
|
519
538
|
return 0.0
|
|
520
539
|
elif isinstance(divergence, dict) and influenced_dof in divergence.keys():
|
|
521
540
|
return divergence[influenced_dof]
|
|
@@ -539,7 +558,7 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
539
558
|
K = hs_set.hydrostatic_stiffness.sel(influenced_dof=list(self.dofs.keys()), radiating_dof=list(self.dofs.keys()))
|
|
540
559
|
return K
|
|
541
560
|
|
|
542
|
-
def compute_rigid_body_inertia(self, *, rho=1000, output_type="body_dofs"):
|
|
561
|
+
def compute_rigid_body_inertia(self, *, rho=1000.0, output_type="body_dofs"):
|
|
543
562
|
"""
|
|
544
563
|
Inertia Mass matrix of the body for 6 rigid DOFs.
|
|
545
564
|
|
|
@@ -629,8 +648,8 @@ class FloatingBody(ClippableMixin, Abstract3DObject):
|
|
|
629
648
|
|
|
630
649
|
if output_type == "body_dofs":
|
|
631
650
|
if len(non_rigid_dofs) > 0:
|
|
632
|
-
LOG.warning(f"Non-rigid dofs
|
|
633
|
-
|
|
651
|
+
LOG.warning(f"Non-rigid dofs {non_rigid_dofs} detected: their \
|
|
652
|
+
inertia coefficients are assigned as NaN.")
|
|
634
653
|
|
|
635
654
|
inertia_matrix_xr = total_mass_xr.sel(influenced_dof=body_dof_names,
|
|
636
655
|
radiating_dof=body_dof_names)
|
|
@@ -732,7 +751,17 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
732
751
|
def join_bodies(*bodies, name=None) -> 'FloatingBody':
|
|
733
752
|
if name is None:
|
|
734
753
|
name = "+".join(body.name for body in bodies)
|
|
735
|
-
meshes = CollectionOfMeshes(
|
|
754
|
+
meshes = CollectionOfMeshes(
|
|
755
|
+
[body.mesh for body in bodies],
|
|
756
|
+
name=f"{name}_mesh"
|
|
757
|
+
)
|
|
758
|
+
if all(body.lid_mesh is None for body in bodies):
|
|
759
|
+
lid_meshes = None
|
|
760
|
+
else:
|
|
761
|
+
lid_meshes = CollectionOfMeshes(
|
|
762
|
+
[body.lid_mesh for body in bodies if body.lid_mesh is not None],
|
|
763
|
+
name=f"{name}_lid_mesh"
|
|
764
|
+
)
|
|
736
765
|
dofs = FloatingBody.combine_dofs(bodies)
|
|
737
766
|
|
|
738
767
|
if all(body.mass is not None for body in bodies):
|
|
@@ -747,7 +776,8 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
747
776
|
new_cog = None
|
|
748
777
|
|
|
749
778
|
joined_bodies = FloatingBody(
|
|
750
|
-
mesh=meshes,
|
|
779
|
+
mesh=meshes, lid_mesh=lid_meshes, dofs=dofs,
|
|
780
|
+
mass=new_mass, center_of_mass=new_cog, name=name
|
|
751
781
|
)
|
|
752
782
|
|
|
753
783
|
for matrix_name in ["inertia_matrix", "hydrostatic_stiffness"]:
|
|
@@ -823,7 +853,6 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
823
853
|
if not isinstance(locations, np.ndarray):
|
|
824
854
|
raise TypeError('locations must be of type np.ndarray')
|
|
825
855
|
assert locations.shape[1] == 2, 'locations must be of shape nx2, received {:}'.format(locations.shape)
|
|
826
|
-
n = locations.shape[0]
|
|
827
856
|
|
|
828
857
|
fb_list = []
|
|
829
858
|
for idx, li in enumerate(locations):
|
|
@@ -839,6 +868,7 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
839
868
|
def extract_faces(self, id_faces_to_extract, return_index=False):
|
|
840
869
|
"""Create a new FloatingBody by extracting some faces from the mesh.
|
|
841
870
|
The dofs evolve accordingly.
|
|
871
|
+
The lid_mesh, center_of_mass, mass and hydrostatics data are discarded.
|
|
842
872
|
"""
|
|
843
873
|
if isinstance(self.mesh, CollectionOfMeshes):
|
|
844
874
|
raise NotImplementedError # TODO
|
|
@@ -860,7 +890,12 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
860
890
|
return new_body
|
|
861
891
|
|
|
862
892
|
def sliced_by_plane(self, plane):
|
|
863
|
-
|
|
893
|
+
"""Return the same body, but replace the mesh by a set of two meshes
|
|
894
|
+
corresponding to each sides of the plane."""
|
|
895
|
+
return FloatingBody(mesh=self.mesh.sliced_by_plane(plane),
|
|
896
|
+
lid_mesh=self.lid_mesh.sliced_by_plane(plane)
|
|
897
|
+
if self.lid_mesh is not None else None,
|
|
898
|
+
dofs=self.dofs, name=self.name)
|
|
864
899
|
|
|
865
900
|
def minced(self, nb_slices=(8, 8, 4)):
|
|
866
901
|
"""Experimental method decomposing the mesh as a hierarchical structure.
|
|
@@ -921,27 +956,35 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
921
956
|
@inplace_transformation
|
|
922
957
|
def mirror(self, plane):
|
|
923
958
|
self.mesh.mirror(plane)
|
|
959
|
+
if self.lid_mesh is not None:
|
|
960
|
+
self.lid_mesh.mirror(plane)
|
|
924
961
|
for dof in self.dofs:
|
|
925
962
|
self.dofs[dof] -= 2 * np.outer(np.dot(self.dofs[dof], plane.normal), plane.normal)
|
|
926
963
|
for point_attr in ('geometric_center', 'rotation_center', 'center_of_mass'):
|
|
927
964
|
if point_attr in self.__dict__ and self.__dict__[point_attr] is not None:
|
|
928
|
-
|
|
965
|
+
point = np.array(self.__dict__[point_attr])
|
|
966
|
+
shift = - 2 * (np.dot(point, plane.normal) - plane.c) * plane.normal
|
|
967
|
+
self.__dict__[point_attr] = point + shift
|
|
929
968
|
return self
|
|
930
969
|
|
|
931
970
|
@inplace_transformation
|
|
932
971
|
def translate(self, vector, *args, **kwargs):
|
|
933
972
|
self.mesh.translate(vector, *args, **kwargs)
|
|
973
|
+
if self.lid_mesh is not None:
|
|
974
|
+
self.lid_mesh.translate(vector, *args, **kwargs)
|
|
934
975
|
for point_attr in ('geometric_center', 'rotation_center', 'center_of_mass'):
|
|
935
976
|
if point_attr in self.__dict__ and self.__dict__[point_attr] is not None:
|
|
936
|
-
self.__dict__[point_attr]
|
|
977
|
+
self.__dict__[point_attr] = np.array(self.__dict__[point_attr]) + vector
|
|
937
978
|
return self
|
|
938
979
|
|
|
939
980
|
@inplace_transformation
|
|
940
981
|
def rotate(self, axis, angle):
|
|
941
982
|
self.mesh.rotate(axis, angle)
|
|
983
|
+
if self.lid_mesh is not None:
|
|
984
|
+
self.lid_mesh.rotate(axis, angle)
|
|
942
985
|
for point_attr in ('geometric_center', 'rotation_center', 'center_of_mass'):
|
|
943
986
|
if point_attr in self.__dict__ and self.__dict__[point_attr] is not None:
|
|
944
|
-
self.__dict__[point_attr] = axis.rotate_points([self.__dict__[point_attr]], angle)
|
|
987
|
+
self.__dict__[point_attr] = axis.rotate_points([self.__dict__[point_attr]], angle)[0, :]
|
|
945
988
|
for dof in self.dofs:
|
|
946
989
|
self.dofs[dof] = axis.rotate_vectors(self.dofs[dof], angle)
|
|
947
990
|
return self
|
|
@@ -951,6 +994,8 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
951
994
|
# Clip mesh
|
|
952
995
|
LOG.info(f"Clipping {self.name} with respect to {plane}")
|
|
953
996
|
self.mesh.clip(plane)
|
|
997
|
+
if self.lid_mesh is not None:
|
|
998
|
+
self.lid_mesh.clip(plane)
|
|
954
999
|
|
|
955
1000
|
# Clip dofs
|
|
956
1001
|
ids = self.mesh._clipping_data['faces_ids']
|
|
@@ -966,15 +1011,52 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
966
1011
|
# Display #
|
|
967
1012
|
#############
|
|
968
1013
|
|
|
1014
|
+
def __short_str__(self):
|
|
1015
|
+
return (f"{self.__class__.__name__}(..., name=\"{self.name}\")")
|
|
1016
|
+
|
|
1017
|
+
def _optional_params_str(self):
|
|
1018
|
+
items = []
|
|
1019
|
+
if self.mass is not None: items.append(f"mass={self.mass}, ")
|
|
1020
|
+
if self.center_of_mass is not None: items.append(f"center_of_mass={self.center_of_mass}, ")
|
|
1021
|
+
return ''.join(items)
|
|
1022
|
+
|
|
969
1023
|
def __str__(self):
|
|
970
|
-
|
|
1024
|
+
short_dofs = '{' + ', '.join('"{}": ...'.format(d) for d in self.dofs) + '}'
|
|
1025
|
+
|
|
1026
|
+
if self.lid_mesh is not None:
|
|
1027
|
+
lid_mesh_str = self.lid_mesh.__short_str__()
|
|
1028
|
+
else:
|
|
1029
|
+
lid_mesh_str = str(None)
|
|
1030
|
+
|
|
1031
|
+
return (f"{self.__class__.__name__}(mesh={self.mesh.__short_str__()}, lid_mesh={lid_mesh_str}, "
|
|
1032
|
+
f"dofs={short_dofs}, {self._optional_params_str()}name=\"{self.name}\")")
|
|
971
1033
|
|
|
972
1034
|
def __repr__(self):
|
|
973
|
-
|
|
974
|
-
|
|
1035
|
+
short_dofs = '{' + ', '.join('"{}": ...'.format(d) for d in self.dofs) + '}'
|
|
1036
|
+
|
|
1037
|
+
if self.lid_mesh is not None:
|
|
1038
|
+
lid_mesh_str = str(self.lid_mesh)
|
|
1039
|
+
else:
|
|
1040
|
+
lid_mesh_str = str(None)
|
|
1041
|
+
|
|
1042
|
+
return (f"{self.__class__.__name__}(mesh={str(self.mesh)}, lid_mesh={lid_mesh_str}, "
|
|
1043
|
+
f"dofs={short_dofs}, {self._optional_params_str()}name=\"{self.name}\")")
|
|
975
1044
|
|
|
976
1045
|
def _repr_pretty_(self, p, cycle):
|
|
977
|
-
p.text(self.
|
|
1046
|
+
p.text(self.__str__())
|
|
1047
|
+
|
|
1048
|
+
def __rich_repr__(self):
|
|
1049
|
+
class DofWithShortRepr:
|
|
1050
|
+
def __repr__(self):
|
|
1051
|
+
return '...'
|
|
1052
|
+
yield "mesh", self.mesh
|
|
1053
|
+
yield "lid_mesh", self.lid_mesh
|
|
1054
|
+
yield "dofs", {d: DofWithShortRepr() for d in self.dofs}
|
|
1055
|
+
if self.mass is not None:
|
|
1056
|
+
yield "mass", self.mass, None
|
|
1057
|
+
if self.center_of_mass is not None:
|
|
1058
|
+
yield "center_of_mass", tuple(self.center_of_mass)
|
|
1059
|
+
yield "name", self.name
|
|
978
1060
|
|
|
979
1061
|
def show(self, **kwargs):
|
|
980
1062
|
from capytaine.ui.vtk.body_viewer import FloatingBodyViewer
|
|
@@ -1013,5 +1095,91 @@ respective inertia coefficients are assigned as NaN.")
|
|
|
1013
1095
|
@property
|
|
1014
1096
|
def minimal_computable_wavelength(self):
|
|
1015
1097
|
"""For accuracy of the resolution, wavelength should not be smaller than this value."""
|
|
1016
|
-
|
|
1098
|
+
if self.lid_mesh is not None:
|
|
1099
|
+
return max(8*self.mesh.faces_radiuses.max(), 8*self.lid_mesh.faces_radiuses.max())
|
|
1100
|
+
else:
|
|
1101
|
+
return 8*self.mesh.faces_radiuses.max()
|
|
1102
|
+
|
|
1103
|
+
def first_irregular_frequency_estimate(self, *, g=9.81):
|
|
1104
|
+
r"""Estimates the angular frequency of the lowest irregular
|
|
1105
|
+
frequency.
|
|
1106
|
+
This is based on the formula for the lowest irregular frequency of a
|
|
1107
|
+
parallelepiped of size :math:`L \times B` and draft :math:`H`:
|
|
1108
|
+
|
|
1109
|
+
.. math::
|
|
1110
|
+
\omega = \sqrt{
|
|
1111
|
+
\frac{\pi g \sqrt{\frac{1}{B^2} + \frac{1}{L^2}}}
|
|
1112
|
+
{\tanh\left(\pi H \sqrt{\frac{1}{B^2} + \frac{1}{L^2}} \right)}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
The formula is applied to all shapes to get an estimate that is usually
|
|
1116
|
+
conservative.
|
|
1117
|
+
The definition of a lid (supposed to be fully covering and horizontal)
|
|
1118
|
+
is taken into account.
|
|
1119
|
+
"""
|
|
1120
|
+
if self.lid_mesh is None:
|
|
1121
|
+
draft = abs(self.mesh.vertices[:, 2].min())
|
|
1122
|
+
else:
|
|
1123
|
+
draft = abs(self.lid_mesh.vertices[:, 2].min())
|
|
1124
|
+
if draft < 1e-6:
|
|
1125
|
+
return np.inf
|
|
1126
|
+
|
|
1127
|
+
# Look for the x and y span of each components (e.g. for multibody) and
|
|
1128
|
+
# keep the one causing the lowest irregular frequency.
|
|
1129
|
+
# The draft is supposed to be same for all components.
|
|
1130
|
+
omega = np.inf
|
|
1131
|
+
for comp in connected_components(self.mesh):
|
|
1132
|
+
for ccomp in connected_components_of_waterline(comp):
|
|
1133
|
+
x_span = ccomp.vertices[:, 0].max() - ccomp.vertices[:, 0].min()
|
|
1134
|
+
y_span = ccomp.vertices[:, 1].max() - ccomp.vertices[:, 1].min()
|
|
1135
|
+
p = np.hypot(1/x_span, 1/y_span)
|
|
1136
|
+
omega_comp = np.sqrt(np.pi*g*p/(np.tanh(np.pi*draft*p)))
|
|
1137
|
+
omega = min(omega, omega_comp)
|
|
1138
|
+
return omega
|
|
1139
|
+
|
|
1140
|
+
def cluster_bodies(*bodies, name=None):
|
|
1141
|
+
"""
|
|
1142
|
+
Builds a hierarchical clustering from a group of bodies
|
|
1143
|
+
|
|
1144
|
+
Parameters
|
|
1145
|
+
----------
|
|
1146
|
+
bodies: list
|
|
1147
|
+
a list of bodies
|
|
1148
|
+
name: str, optional
|
|
1149
|
+
a name for the new body
|
|
1150
|
+
|
|
1151
|
+
Returns
|
|
1152
|
+
-------
|
|
1153
|
+
FloatingBody
|
|
1154
|
+
Array built from the provided bodies
|
|
1155
|
+
"""
|
|
1156
|
+
from scipy.cluster.hierarchy import linkage
|
|
1157
|
+
nb_buoys = len(bodies)
|
|
1158
|
+
|
|
1159
|
+
if any(body.center_of_buoyancy is None for body in bodies):
|
|
1160
|
+
raise ValueError("The center of buoyancy of each body needs to be known for clustering")
|
|
1161
|
+
buoys_positions = np.stack([body.center_of_buoyancy for body in bodies])[:,:2]
|
|
1162
|
+
|
|
1163
|
+
ln_matrix = linkage(buoys_positions, method='centroid', metric='euclidean')
|
|
1164
|
+
|
|
1165
|
+
node_list = list(bodies) # list of nodes of the tree: the first nodes are single bodies
|
|
1166
|
+
|
|
1167
|
+
# Join the bodies, with an ordering consistent with the dendrogram.
|
|
1168
|
+
# Done by reading the linkage matrix: its i-th row contains the labels
|
|
1169
|
+
# of the two nodes that are merged to form the (n + i)-th node
|
|
1170
|
+
for ii in range(len(ln_matrix)):
|
|
1171
|
+
node_tag = ii + nb_buoys # the first nb_buoys tags are already taken
|
|
1172
|
+
merge_left = int(ln_matrix[ii,0])
|
|
1173
|
+
merge_right = int(ln_matrix[ii,1])
|
|
1174
|
+
# The new node is the parent of merge_left and merge_right
|
|
1175
|
+
new_node_ls = [node_list[merge_left], node_list[merge_right]]
|
|
1176
|
+
new_node = FloatingBody.join_bodies(*new_node_ls, name='node_{:d}'.format(node_tag))
|
|
1177
|
+
node_list.append(new_node)
|
|
1178
|
+
|
|
1179
|
+
# The last node is the parent of all others
|
|
1180
|
+
all_buoys = new_node
|
|
1181
|
+
|
|
1182
|
+
if name is not None:
|
|
1183
|
+
all_buoys.name = name
|
|
1017
1184
|
|
|
1185
|
+
return all_buoys
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env python
|
|
2
|
-
# coding: utf-8
|
|
3
1
|
"""Abstract structure of a class used to compute the Green function"""
|
|
4
2
|
# Copyright (C) 2017-2019 Matthieu Ancellin
|
|
5
3
|
# See LICENSE file at <https://github.com/mancellin/capytaine>
|
|
@@ -12,4 +10,3 @@ class AbstractGreenFunction(ABC):
|
|
|
12
10
|
@abstractmethod
|
|
13
11
|
def evaluate(self, mesh1, mesh2, free_surface, sea_bottom, wavenumber):
|
|
14
12
|
pass
|
|
15
|
-
|