# Copyright (C) 2017-2022 Matthieu Ancellin
# See LICENSE file at <https://github.com/capytaine/capytaine>
from abc import ABC, abstractmethod
from functools import lru_cache
import logging
import numpy as np
import xarray as xr
LOG = logging.getLogger(__name__)
[docs]
class AbstractDof(ABC):
[docs]
@abstractmethod
def evaluate_motion(self, mesh):
...
[docs]
@abstractmethod
def evaluate_gradient_of_motion(self, mesh):
...
[docs]
class TranslationDof(AbstractDof):
def __init__(self, direction):
self.direction = np.asarray(direction)
assert self.direction.shape == (3,)
def __str__(self):
return f"TranslationDof(direction={self.direction})"
[docs]
@lru_cache
def evaluate_motion(self, mesh) -> np.array:
return np.tile(self.direction, (mesh.nb_faces, 1))
[docs]
@lru_cache
def evaluate_gradient_of_motion(self, mesh) -> np.array:
return np.zeros((mesh.nb_faces, 3, 3))
[docs]
class RotationDof(AbstractDof):
def __init__(self, rotation_center, direction):
self.direction = np.asarray(direction)
assert self.direction.shape == (3,)
if rotation_center is None:
self.rotation_center = np.array([0, 0, 0])
LOG.warning("Rigid body rotation dof has been initialized "
"around the origin of the domain (0, 0, 0).")
else:
self.rotation_center = np.asarray(rotation_center)
assert self.rotation_center.shape == (3,)
def __str__(self):
return f"RotationDof(rotation_center={self.rotation_center}, direction={self.direction})"
[docs]
@lru_cache
def evaluate_motion(self, mesh) -> np.array:
if mesh.nb_faces == 0:
return np.empty((mesh.nb_faces, 3))
else:
motion = np.cross(self.direction, mesh.faces_centers - self.rotation_center)
return motion
[docs]
@lru_cache
def evaluate_gradient_of_motion(self, mesh) -> np.array:
grad = np.cross(self.direction, np.eye(3))
return np.tile(grad, (mesh.nb_faces, 1, 1))
[docs]
class DofOnSubmesh(AbstractDof):
def __init__(self, dof: AbstractDof, faces):
self.dof = dof
self.faces = faces
[docs]
def evaluate_motion(self, mesh):
motion = np.zeros((mesh.nb_faces, 3))
motion[self.faces, :] = self.dof.evaluate_motion(mesh.extract_faces(self.faces))
return motion
[docs]
def evaluate_gradient_of_motion(self, mesh) -> np.array:
grad = np.zeros((mesh.nb_faces, 3, 3))
grad[self.faces, :, :] = self.dof.evaluate_gradient_of_motion(mesh.extract_faces(self.faces))
return grad
[docs]
def is_rigid_body_dof(dof):
return (
isinstance(dof, TranslationDof)
or isinstance(dof, RotationDof)
# or (isinstance(dof, DofOnSubmesh) and is_rigid_body_dof(dof.dof))
)
[docs]
def rigid_body_dofs(only=None, rotation_center=None):
"""Pass this to FloatingBody initializer to give it rigid body dofs.
Parameters
----------
only: sequence of str, optional
list of the name of the rigid body dofs to be included.
By default: all six of them
rotation_center: np.array, optional
the center for the definition of the rotations
"""
if rotation_center is None:
rotation_center = np.array([0, 0, 0])
if not (only is not None
and set(only).issubset({"Surge", "Sway", "Heave"})
): # Skip the warning if only translations are required.
LOG.warning("Rigid body rotation dofs have been initialized "
"around the origin of the domain (0, 0, 0).")
# This warning is redundant with the one in RotationDof.__init__,
# but it is done here to have a single warning displayed on screen
# when a rigid body is initialized.
dofs = {
"Surge": TranslationDof(direction=(1, 0, 0)),
"Sway": TranslationDof(direction=(0, 1, 0)),
"Heave": TranslationDof(direction=(0, 0, 1)),
"Roll": RotationDof(rotation_center=rotation_center, direction=(1, 0, 0)),
"Pitch": RotationDof(rotation_center=rotation_center, direction=(0, 1, 0)),
"Yaw": RotationDof(rotation_center=rotation_center, direction=(0, 0, 1)),
}
if only is not None:
dofs = {k: v for k, v in dofs.items() if k in only}
return dofs
[docs]
def normalize_name(name):
return name[0].upper() + name[1:].lower()
[docs]
def add_dofs_labels_to_vector(dof_names, vector):
"""Helper function turning a bare vector into a vector labelled by the name of the dofs,
to be used for instance for the computation of RAO."""
return xr.DataArray(data=np.asarray(vector), dims=['influenced_dof'],
coords={'influenced_dof': list(dof_names)},
)
[docs]
def add_dofs_labels_to_matrix(dof_names, matrix):
"""Helper function turning a bare matrix into a matrix labelled by the name of the dofs,
to be used for instance for the computation of RAO."""
return xr.DataArray(data=np.asarray(matrix), dims=['influenced_dof', 'radiating_dof'],
coords={'influenced_dof': list(dof_names), 'radiating_dof': list(dof_names)},
)