Esempio n. 1
0
 def setUp(self):
     config = {
         'drag coefficient': 0.7,
         'inertia coefficient': 2.0,
         'members': [{
             'end1': [0, 0, 0],
             'end2': [0, 0, -10],
             'diameter': 2.3,
             'strip width': 1,
         }],
     }
     self.model = ViscousDragModel(config)
Esempio n. 2
0
def Morison_added_mass(w, draft, radius, Cm=2.0):
    """Calculate added mass and damping from Morison strip model of
    uniform cylinder"""
    from whales.viscous_drag import ViscousDragModel
    Morison_model = ViscousDragModel({
        'inertia coefficient':
        Cm,
        'members': [{
            'end1': [0, 0, 0],
            'end2': [0, 0, -draft],
            'diameter': 2 * radius,
            'strip width': 1.0,
        }]
    })
    A1 = Morison_model.Morison_added_mass()
    A = np.tile(A1, (len(w), 1, 1))
    return LinearSystem(A, zeros_like(A), zeros_like(A))
Esempio n. 3
0
def excitation_force(w, z1, z2, diameter, Cm=2):
    """Calculate the excitation force. Specific for now to vertical
    cylinders of varying diameter.

    ``z1``, ``z2``: coordinates of ends of element
    ``diameter``: array (2 x Npoints) diameter at each z value
    """

    config = {
        'end1': [0, 0, z1],
        'end2': [0, 0, z2],
        'diameter': diameter,
        'strip width': 1.0,
    }
    Morison_model = ViscousDragModel({
        'inertia coefficient': Cm,
        'members': [config],
    })
    X = Morison_model.Morison_inertial_force(w)
    return X
Esempio n. 4
0
 def setUp(self):
     config = {
         'drag coefficient': 0.7,
         'members': [{
             'end1': [0, 0, 0],
             'end2': [0, 0, -10],
             'diameter': [[0, 3, 5, 10], [2.2, 2.2, 5, 5]],
             'strip width': 0.5,
         }],
     }
     self.model = ViscousDragModel(config)
Esempio n. 5
0
def added_mass(w, z1, z2, diameter, Cm=2):
    """Calculate the added mass/damping. Specific for now to vertical
    cylinders of varying diameter.

    ``z1``, ``z2``: coordinates of ends of element
    ``diameter``: array (2 x Npoints) diameter at each z value
    """

    config = {
        'end1': [0, 0, z1],
        'end2': [0, 0, z2],
        'diameter': diameter,
        'strip width': 1.0,
    }
    Morison_model = ViscousDragModel({
        'inertia coefficient': Cm,
        'members': [config],
    })
    A1 = Morison_model.Morison_added_mass()
    A = np.tile(A1, (len(w), 1, 1))
    return LinearSystem(A, zeros_like(A), zeros_like(A))
Esempio n. 6
0
    def __init__(self, config, frequency_values):
        self.config = config
        self.w = frequency_values

        # Build structural model
        self.structure = FloatingTurbineStructure(config['structure'])

        # Get hydrodynamic/static information
        h = config['hydrodynamics']
        rho = config['constants']['water density']
        first_order = WAMITData(h['WAMIT path'], water_density=rho)
        if 'QTF path' in h:
            second_order = SecondOrderData(h['QTF path'], water_density=rho)
        else:
            second_order = None
        self.hydro_info = HydrodynamicsInfo(first_order, second_order)

        # Get mooring line behaviour
        l = config['mooring']['linear']
        self.mooring_static = np.asarray(l['static force'])
        self.mooring_stiffness = np.asarray(l['stiffness'])

        # Extra damping
        self.B_extra = np.asarray(h.get('extra damping', np.zeros(6)))
        if self.B_extra.ndim == 1:
            self.B_extra = np.diag(self.B_extra)

        # Viscous drag model
        if 'Morison elements' in h:
            self.morison = ViscousDragModel(h['Morison elements'])
        else:
            self.morison = None
        self.Bv = np.zeros((6, 6))
        self.Fvc = np.zeros(6)
        self.Fvv = np.zeros((len(self.w), 6))

        # Wave-drift damping
        self.Bwd = np.zeros((6, 6))
Esempio n. 7
0
 def setUp(self):
     config = {
         'drag coefficient': 0.7,
         'members': [{
         #     # Member in x-direction
         #     'end1': [0, 0, 0],
         #     'end2': [1, 0, 0],
         #     'diameter': 1,
         #     'strip width': 1,
         # }, {
             # Member in z-direction
             'end1': [0, 0, 0],
             'end2': [0, 0, 1],
             'diameter': 1,
             'strip width': 1,
         # }, {
         #     # Member in xy plane at 30deg from x axis
         #     'end1': [0, 0, 0],
         #     'end2': [np.cos(30*np.pi/180), np.sin(30*np.pi/180), 0],
         #     'diameter': 1,
         #     'strip width': 1,
         }],
     }
     self.model = ViscousDragModel(config)
Esempio n. 8
0
class FloatingTurbineModel(object):
    def __init__(self, config, frequency_values):
        self.config = config
        self.w = frequency_values

        # Build structural model
        self.structure = FloatingTurbineStructure(config['structure'])

        # Get hydrodynamic/static information
        h = config['hydrodynamics']
        rho = config['constants']['water density']
        first_order = WAMITData(h['WAMIT path'], water_density=rho)
        if 'QTF path' in h:
            second_order = SecondOrderData(h['QTF path'], water_density=rho)
        else:
            second_order = None
        self.hydro_info = HydrodynamicsInfo(first_order, second_order)

        # Get mooring line behaviour
        l = config['mooring']['linear']
        self.mooring_static = np.asarray(l['static force'])
        self.mooring_stiffness = np.asarray(l['stiffness'])

        # Extra damping
        self.B_extra = np.asarray(h.get('extra damping', np.zeros(6)))
        if self.B_extra.ndim == 1:
            self.B_extra = np.diag(self.B_extra)

        # Viscous drag model
        if 'Morison elements' in h:
            self.morison = ViscousDragModel(h['Morison elements'])
        else:
            self.morison = None
        self.Bv = np.zeros((6, 6))
        self.Fvc = np.zeros(6)
        self.Fvv = np.zeros((len(self.w), 6))

        # Wave-drift damping
        self.Bwd = np.zeros((6, 6))

    def calculate_viscous_effects(self, S_wave):
        """
        Calculate viscous forces and damping with the given wave spectrum.

        Note that this can be iterative, as the transfer functions will be
        re-calculated using previously-calculated viscous effects.
        """
        assert len(S_wave) == len(self.w)
        H_wave = self.transfer_function_from_wave_elevation()
        self.Bv, self.Fvc, self.Fvv = self.morison.total_drag(self.w, H_wave,
                                                              S_wave)

        # Fvv is the actual force/unit wave height at each wave
        # frequency -- to get the spectrum, it's like the first-order
        # wave force spectrum.
        self.viscous_force_spectrum = response_spectrum(self.Fvv, S_wave)

    def calculate_wave_drift_damping(self, S_wave):
        """
        Calculate and save wave-drift damping matrix
        """
        assert len(S_wave) == len(self.w)
        self.Bwd = self.hydro_info.wave_drift_damping(self.w, S_wave)

    def linearised_matrices(self, w, **kwargs):
        """
        Linearise the structural model and add in the hydrodynamic
        added-mass and damping for the given frequency ``w``, as well
        as the hydrostatic stiffness, wave-drift damping and viscous damping.
        """
        # Set default perturbation
        kwargs.setdefault('perturbation', 1e-4)

        # Structural - includes gravitational stiffness
        M, B, C, = self.structure.linearised_matrices(**kwargs)
        M[:6, :6] += self.hydro_info.A(w)
        B[:6, :6] += self.hydro_info.B(w) + self.Bv + self.Bwd + self.B_extra
        C[:6, :6] += self.hydro_info.C + self.mooring_stiffness
        return M, B, C

    def coupled_modes(self, w, ignore_damping=False, **kwargs):
        M, B, C = self.linearised_matrices(w, **kwargs)
        if ignore_damping:
            wn, vn = linalg.eig(C, M)
            order = np.argsort(abs(wn))
            wn = np.sqrt(abs(wn[order]))
            vn = vn[:, order]
        else:
            AA = r_[c_[zeros_like(C), C], c_[C, B]]
            BB = r_[c_[C, zeros_like(C)], c_[zeros_like(C), -M]]
            wn, vn = linalg.eig(AA, BB)
            order = np.argsort(abs(wn))
            wn = abs(wn[order])
            # Mode shapes are the first half of the rows; the second
            # half of the rows should be same multiplied by eigenvalues.
            vn = vn[:M.shape[0], order]

            # We expect all the modes to be complex conjugate; return
            # every other one.
            # First: make sure all are the same sign
            norm_vn = vn / vn[np.argmax(abs(vn), axis=0), range(vn.shape[1])]
            assert (np.allclose(wn[::2], wn[1::2], rtol=1e-4) and
                    np.allclose(norm_vn[:, ::2], norm_vn[:, 1::2].conj(), atol=1e-2)), \
                "Expect conjugate modes"
            wn = wn[::2]
            vn = norm_vn[:, ::2]
        return wn, vn

    def transfer_function(self, rotor_speed=None):
        """
        Linearise the structural model and return multi-dof transfer function
        for the structure, including mooring lines and hydrodynamics, at the
        given frequencies ``ws``
        """

        # Structural - includes gravitational stiffness
        if rotor_speed is not None:
            M_struct, B_struct, C_struct = self.structure.linearised_matrices(
                zd0={'shaft': [rotor_speed]}, mbc=True)
        else:
            M_struct, B_struct, C_struct = self.structure.linearised_matrices()

        # Full matrices to be assembled at each frequency
        Mi = zeros_like(M_struct)
        Bi = zeros_like(B_struct)
        Ci = zeros_like(C_struct)

        # Stiffness is constant -- add hydrostatics and mooring lines
        hh = self.hydro_info  # shorthand
        Ci[:, :] = C_struct
        Ci[:6, :6] += hh.C + self.mooring_stiffness

        # Calculate transfer function at each frequency
        H = np.empty((len(self.w),) + M_struct.shape, dtype=np.complex)
        for i, w in enumerate(self.w):
            Mi[:, :] = M_struct
            Bi[:, :] = B_struct
            Mi[:6, :6] += hh.A(w)  # add rigid-body parts
            Bi[:6, :6] += hh.B(w) + self.Bv + self.Bwd + self.B_extra
            H[i, :, :] = linalg.inv(-(w**2)*Mi + 1j*w*Bi + Ci)
        return H

    def transfer_function_from_wave_elevation(self, rotor_speed=None, heading=0):
        """
        Calculate the transfer function from wave elevation to response,
        similar to ``transfer_function(ws)`` but including the wave excitation
        force ``X``.
        """
        H = self.transfer_function(rotor_speed)
        X = self.hydro_info.X(self.w, heading)  # interpolate

        # Add in the viscous drag force due to waves
        X += self.Fvv
        X[self.w == 0] += self.Fvc  # constant drag force

        # Multiply transfer functions to get overall transfer function
        # (H can be larger than X if the model is flexible)
        H_wave = np.einsum('wij,wj->wi', H[:, :, :6], X)
        return H_wave

    def response_spectrum(self, S_wave, second_order=True, viscous=True):
        """Convenience method which calculates the response spectrum
        """
        S1 = self.hydro_info.first_order_force_spectrum(self.w, S_wave)
        if second_order:
            S2 = self.hydro_info.second_order_force_spectrum(self.w, S_wave)
        else:
            S2 = zeros_like(S1)
        if viscous:
            self.calculate_viscous_effects(S_wave)
            Sv = self.viscous_force_spectrum
        else:
            Sv = zeros_like(S1)
        self.calculate_wave_drift_damping(S_wave)

        # XXX this doesn't work well if second_order or viscous is set
        # to False -- transfer_function() will use previously-calculated values

        H = self.transfer_function()
        SF = S1 + S2 + Sv
        Sx = response_spectrum(H[:, :, :6], SF)
        return Sx

    @classmethod
    def from_yaml(cls, filename, freq):
        # Read the data
        with open(filename) as f:
            config = yaml.safe_load(f)

        # Load the config file from the same directory
        with path(filename).abspath().parent:
            return cls(config, freq)
Esempio n. 9
0
class CylinderTestCase(MyTestCase):
    """Test simple model with vertical cylinder"""

    def setUp(self):
        config = {
            'drag coefficient': 0.7,
            'inertia coefficient': 2.0,
            'members': [{
                'end1': [0, 0, 0],
                'end2': [0, 0, -10],
                'diameter': 2.3,
                'strip width': 1,
            }],
        }
        self.model = ViscousDragModel(config)

    def test_model_elements(self):
        """Check element strips setup correctly"""
        m = self.model
        self.assertArraysEqual(m.element_lengths, np.ones(10))

        # Centres of strips
        centres = np.zeros((10, 3))
        centres[:,2] = -np.arange(0.5, 10, 1)
        self.assertArraysEqual(m.element_centres, centres)

        # Element diameters
        self.assertArraysEqual(m.element_diameters, 2.3)

        # Element axes
        self.assertArraysEqual(m.element_axes, np.array([np.eye(3)] * 10))

    def test_wave_velocity_transfer_func(self):
        """Test wave velocity transfer function"""
        w = np.array([1,2]) # frequencies to test
        H_uf = self.model.wave_velocity_transfer_function(w)

        # With waves in x-direction, sideways velocity should be zero
        self.assertArraysEqual(H_uf[:,:,1], 0)

        # Check variation in depth: exp(kz)
        iz1 = 3
        iz2 = 8
        z = self.model.element_centres[:,2]
        for i in range(2):
            assert_array_almost_equal_nulp(H_uf[i,iz1,:] / np.exp(w[i]**2/9.81*z[iz1]),
                                           H_uf[i,iz2,:] / np.exp(w[i]**2/9.81*z[iz2]))

        # Check all x velocities are in-phase and real, all z are imaginary
        self.assertTrue(np.isreal(     H_uf[:,:,0]).all())
        self.assertTrue(np.isreal(1j * H_uf[:,:,2]).all())

    def test_structural_velocity_transfer_func(self):
        """Test structural velocity with special cases"""
        w = np.array([1,2]) # frequencies to test

        # Case 1: pure surge motion
        H1 = np.zeros((2, 6)) # shape (freq, xyzXYZ)
        H1[:,0] = 1 # maximum surge at maximum datum wave height
        H_us = self.model.structural_velocity_transfer_function(w, H1)
        # all elements should have same surge velocity; all other velocities zero
        # at t=0, velocity is zero and becoming negative 90 deg later
        self.assertArraysEqual(H_us[1,:,0], 2j)
        self.assertArraysEqual(H_us[0,:,0], 1j)
        self.assertArraysEqual(H_us[:,:,1:], 0)

        # Case 2: pure roll motion
        H2 = np.zeros((2, 6)) # shape (freq, xyzXYZ)
        H2[:,3] = 1 # maximum roll at maximum datum wave height
        H_us = self.model.structural_velocity_transfer_function(w, H2)
        # x & z velocity should be zero
        self.assertArraysEqual(H_us[:,:,[0,2]], 0)
        # y velocity corresponding to rotation about origin (check bottom)
        # at t=0, ang. velocity is zero and becoming negative 90 deg later
        # Velocity of bottom element = 9.5 * ang vel
        self.assertArraysEqual(H_us[0,-1,1], 9.5 * 1j)
        self.assertArraysEqual(H_us[1,-1,1], 9.5 * 2j)

    def test_added_mass(self):
        """Test added mass calculation from Morison elements"""

        A = self.model.Morison_added_mass()

        # Expected surge added mass: (Cm-1) * rho * V
        self.assertEqual(A[0,0], 1 * 1025 * 10 * np.pi * 2.3**2 / 4)