Source code for mafredo.helpers

import numpy as np
import xarray as xr
from scipy.optimize import fsolve
from enum import Enum

[docs]class Symmetry(Enum): No = 0 XZ = 1 YZ = 2 XZ_and_YZ = 3 Circular = 4
[docs]class MotionMode(Enum): SURGE = 0 SWAY = 1 HEAVE = 2 ROLL = 3 PITCH = 4 YAW = 5
[docs]def MotionModeToStr(mode: MotionMode) -> str: """These are used in the netcdf databases""" if mode == MotionMode.SURGE: cmode = 'Surge' elif mode == MotionMode.SWAY: cmode = 'Sway' elif mode == MotionMode.HEAVE: cmode = 'Heave' elif mode == MotionMode.ROLL: cmode = 'Roll' elif mode == MotionMode.PITCH: cmode = 'Pitch' elif mode == MotionMode.YAW: cmode = 'Yaw' else: raise ValueError('Unknonwn mode') return cmode
[docs]def wavelength(omega, waterdepth = 0): """Returns the wave-length for this frequency [rad/s] and waterdepth. In deep water the wave-length is 2*pi*g / omega^2 In shallow water the following equations need to be solved numerically: 1. k = 2*pi / wavelength 2. c = sqrt((g/k) * tanh(k*waterdepth)) 3. wavelength = T * c = (2*pi/omega) * c Args: omega : wave-frequency in rad/s waterdepth : waterdepth in [m], use 0 for infinite waterdepth """ if waterdepth==0: return 2*np.pi*9.81 / omega**2 def error(guess_wavelength): k = 2*np.pi / guess_wavelength c = np.sqrt((9.81/k) * np.tanh(k*waterdepth)) wavelength = 2*np.pi*c / omega return guess_wavelength - wavelength x = fsolve(error, 2*np.pi*9.81 / omega**2) return x
[docs]def dof_names_to_numbers(ds): """Converts the names of the DOFS to numbers. This is unfortunately required to make sure that the order of the dofs makes sense when retrieving added mass or damping matrices.""" # check if needed if not isinstance(ds.influenced_dof.values[0], int): if isinstance(ds.radiating_dof.values[0], int): # already done return ds # re-name and order def names_to_ind(dof_names): dof_names[dof_names == 'Surge'] = 0 dof_names[dof_names == 'Sway'] = 1 dof_names[dof_names == 'Heave'] = 2 dof_names[dof_names == 'Roll'] = 3 dof_names[dof_names == 'Pitch'] = 4 dof_names[dof_names == 'Yaw'] = 5 return dof_names dof_names = names_to_ind(ds.influenced_dof.values) ds = ds.assign_coords(influenced_dof=dof_names) dof_names = names_to_ind(ds.radiating_dof.values) ds = ds.assign_coords(radiating_dof=dof_names) ds = ds.sortby('radiating_dof') ds = ds.sortby('influenced_dof') return ds
# def fix_order_dofs(m): # """M can have a single omega, or multiple""" # # modes = ('Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw') # # try: # n_omega = m['omega'].shape[0] # except: # n_omega = 1 # # if n_omega == 1: # r = np.zeros((6, 6), dtype=float) # for i, m1 in enumerate(modes): # for j,m2 in enumerate(modes): # r[i,j] = m.sel( # influenced_dof=m1, radiating_dof=m2) # return r # else: # # r = np.zeros((6,6,n_omega)) # # for i, m1 in enumerate(modes): # for j,m2 in enumerate(modes): # r[i,j,:] = m.sel( # influenced_dof=m1, radiating_dof=m2) # # return r
[docs]def expand_omega_dim_const(dataset, new_omega): """Expands the omega axis of dataset to cover the range of new_omega. Extrapolation is done by repeating the nearest values (ie: keep constant) Returns: expanded dataset """ if min(new_omega) < min(dataset['omega'].values): new_minimum = dataset.sel(omega=min(dataset['omega'].values)) # get the lowest entry new_minimum['omega'] = min(new_omega) # change coordinate dataset = xr.concat([dataset, new_minimum], dim='omega') # and concat if max(new_omega) > max(dataset['omega'].values): new_max = dataset.sel(omega=max(dataset['omega'].values)) # get the highest entry new_max['omega'] = max(new_omega) # change the coordinate dataset = xr.concat([dataset, new_max], dim='omega') # and concat return dataset
[docs]def expand_direction_to_full_range(dataset): """Adds entries at value+360 or values-360 if the wave_direction in the dataset do not span the 0...360 interval Returns: expanded dataset """ headings = dataset.coords['wave_direction'].values if max(headings) - min(headings) < 360: if max(headings) < 360: head_first = headings[0] head360 = dataset.sel(wave_direction=head_first) head360.coords['wave_direction'].values += 360 dataset = xr.concat([dataset, head360], dim='wave_direction') if min(headings) > 0: head_last = headings[-1] head360 = dataset.sel(wave_direction=head_last) head360.coords['wave_direction'].values -= 360 dataset = xr.concat([dataset, head360], dim='wave_direction') return dataset
[docs]def f10(number, tol=1e-12): """Make a length-10 string representing the given number We use {:10g} for numbers (general format) which converts the numbers in the best way: '{:10g}'.format(1325123551512511.0) --> '1.32512e+15' (11 chars) '{:10g}'.format(2.0) --> ' 2' '{:10g}'.format(2) --> ' 2' '{:10g}'.format(-432.0) --> ' -432' '{:10g}'.format(-1.1641532182693481e-10) --> '-1.16415e-10' (12 chars) Numbers with an absolute value below tol are exported as 0 """ # scientific notation if abs(number) < tol: return ' 0' for formatter in ['{:10g}','{:.6g}','{:.5g}','{:.4g}','{:.3g}','{:.2g}']: s = formatter.format(number) if len(s) == 10: return s if len(s) < 10: return (10-len(s))*' '+s raise ValueError(f'Can not convert number {number} to a string with length 10')
if __name__ == "__main__": pass