| name | hydrodynamic-analysis |
| version | 1.0.0 |
| description | Hydrodynamic analysis using BEM, RAOs, added mass, damping, and wave loads for offshore structures |
| author | workspace-hub |
| category | subject-matter-expert |
| tags | ["hydrodynamics","bem","rao","added-mass","damping","wave-loads","wamit","aqwa","orcawave"] |
| platforms | ["engineering"] |
Hydrodynamic Analysis SME Skill
Comprehensive hydrodynamic analysis expertise for offshore floating structures including BEM theory, RAO calculations, added mass/damping, and integration with industry-standard software.
When to Use This Skill
Use hydrodynamic analysis knowledge when:
- RAO calculation - Response Amplitude Operators for vessel motions
- Added mass & damping - Frequency-dependent hydrodynamic coefficients
- Wave loading - Diffraction, Froude-Krylov, radiation forces
- BEM analysis - Boundary Element Method for potential flow
- Software integration - WAMIT, AQWA, OrcaWave workflows
- Frequency domain - Linear wave theory analysis
- Time domain - Convolution for time-domain simulations
Core Knowledge Areas
1. Boundary Element Method (BEM)
Potential Flow Theory:
Governing Equation: ∇²φ = 0 (Laplace equation)
Where:
- φ = velocity potential
- Pressure: p = -ρ ∂φ/∂t - ρgz (Bernoulli)
- Velocity: v = ∇φ
BEM Principles:
def bem_panel_method_concept():
"""
Conceptual explanation of BEM panel method.
Key Steps:
1. Discretize wetted surface into panels
2. Apply Green's function (source/dipole distribution)
3. Satisfy boundary conditions on each panel
4. Solve linear system for unknown potentials
5. Calculate forces from pressure integration
"""
pass
Panel Mesh Quality:
mesh_requirements:
panel_size:
general: "< λ/6"
critical_areas: "< λ/10"
aspect_ratio:
maximum: 3.0
preferred: 1.5
panel_count:
minimum: 2000
typical: 5000-10000
large: 20000+
symmetry:
use_if_possible: true
check: "Ensure port-starboard symmetry"
2. Response Amplitude Operators (RAOs)
RAO Definition:
RAO(ω) = Response Amplitude / Wave Amplitude
Units:
- Translation (surge, sway, heave): m/m
- Rotation (roll, pitch, yaw): rad/m or deg/m
RAO Calculation:
import numpy as np
def calculate_rao_from_hydrodynamic_coefficients(
omega: float,
mass_matrix: np.ndarray,
added_mass: np.ndarray,
damping: np.ndarray,
stiffness: np.ndarray,
wave_excitation: np.ndarray
) -> np.ndarray:
"""
Calculate RAO at frequency omega.
Equation of motion (frequency domain):
[-ω²(M + A(ω)) + iω·B(ω) + K]·RAO = F_wave
Args:
omega: Wave frequency (rad/s)
mass_matrix: 6x6 mass matrix
added_mass: 6x6 added mass matrix at omega
damping: 6x6 damping matrix at omega
stiffness: 6x6 hydrostatic stiffness
wave_excitation: 6x1 complex wave excitation force
Returns:
6x1 complex RAO (amplitude and phase)
"""
K_dynamic = (
-omega**2 * (mass_matrix + added_mass) +
1j * omega * damping +
stiffness
)
rao_complex = np.linalg.solve(K_dynamic, wave_excitation)
return rao_complex
omega = 2 * np.pi / 10
M = np.diag([150000, 150000, 150000, 1e7, 1e7, 5e6])
A = np.diag([15000, 15000, 50000, 1e6, 1e6, 5e5])
B = np.diag([50000, 50000, 100000, 5e5, 5e5, 2e5])
K = np.diag([0, 0, 3000, , , ])
F_wave = np.array([, , , , , ]) +
rao = calculate_rao_from_hydrodynamic_coefficients(omega, M, A, B, K, F_wave)
heave_rao_amplitude = np.(rao[])
heave_rao_phase = np.angle(rao[], deg=)
()
RAO Peak Period:
def find_rao_peak_period(
frequencies: np.ndarray,
rao_amplitude: np.ndarray
) -> dict:
"""
Find peak RAO period and resonance characteristics.
Args:
frequencies: Frequency array (rad/s)
rao_amplitude: RAO amplitude array
Returns:
Peak information
"""
peak_idx = np.argmax(rao_amplitude)
peak_omega = frequencies[peak_idx]
peak_period = 2 * np.pi / peak_omega
peak_rao = rao_amplitude[peak_idx]
return {
'peak_frequency_rad_s': peak_omega,
'peak_period_s': peak_period,
'peak_rao': peak_rao,
'resonance_detected': peak_rao > 2.0
}
frequencies = np.linspace(0.1, 2.0, 100)
rao_amplitudes = 1.5 / np.sqrt((1 - (frequencies/0.5)**2)**2 + (0.1*frequencies/0.5)**2)
peak_info = find_rao_peak_period(frequencies, rao_amplitudes)
print(f"Natural period: {peak_info['peak_period_s']:.2f} s")
print(f"Peak RAO: {peak_info['peak_rao']:.2f} m/m")
3. Added Mass and Damping
Frequency-Dependent Coefficients:
def interpolate_hydrodynamic_coefficients(
omega_target: float,
omega_data: np.ndarray,
coefficient_data: np.ndarray
) -> np.ndarray:
"""
Interpolate added mass or damping at target frequency.
Args:
omega_target: Target frequency (rad/s)
omega_data: Frequency data from BEM
coefficient_data: Coefficient matrix (n_freq x 6 x 6)
Returns:
Interpolated 6x6 coefficient matrix
"""
from scipy.interpolate import interp1d
coefficient_interp = np.zeros((6, 6))
for i in range(6):
for j in range(6):
coef_series = coefficient_data[:, i, j]
interpolator = interp1d(
omega_data,
coef_series,
kind='cubic',
fill_value='extrapolate'
)
coefficient_interp[i, j] = interpolator(omega_target)
return coefficient_interp
omega_data = np.array([0.1, 0.5, 1.0, 1.5, 2.0])
added_mass_data = np.random.rand(5, 6, 6) * 10000
A_interp = interpolate_hydrodynamic_coefficients(
omega_target=2*np.pi/8,
omega_data=omega_data,
coefficient_data=added_mass_data
)
()
(A_interp)
Infinite Frequency Added Mass:
def calculate_infinite_frequency_added_mass(
omega_data: np.ndarray,
added_mass_data: np.ndarray
) -> np.ndarray:
"""
Estimate infinite frequency added mass (A_inf).
A_inf = lim(ω→∞) A(ω)
Args:
omega_data: Frequency array
added_mass_data: Added mass array (n_freq x 6 x 6)
Returns:
6x6 infinite frequency added mass
"""
A_inf = np.zeros((6, 6))
for i in range(6):
for j in range(6):
A_inf[i, j] = np.mean(added_mass_data[-3:, i, j])
return A_inf
4. Wave Forces
Froude-Krylov Force:
def calculate_froude_krylov_force(
wave_amplitude: float,
omega: float,
waterplane_area: float,
center_of_buoyancy_depth: float,
rho: float = 1025
) -> float:
"""
Calculate Froude-Krylov force (undisturbed wave pressure).
F_FK = ρ g ζ_a A_wp e^(k z_b)
Args:
wave_amplitude: Wave amplitude (m)
omega: Wave frequency (rad/s)
waterplane_area: Waterplane area (m²)
center_of_buoyancy_depth: Depth of center of buoyancy (m)
rho: Water density (kg/m³)
Returns:
Froude-Krylov force amplitude (N)
"""
g = 9.81
k = omega**2 / g
F_FK = rho * g * wave_amplitude * waterplane_area * np.exp(k * center_of_buoyancy_depth)
return F_FK
F_FK = calculate_froude_krylov_force(
wave_amplitude=2.0,
omega=2*np.pi/10,
waterplane_area=15000,
center_of_buoyancy_depth=-10
)
print(f"Froude-Krylov force: {F_FK/1e6:.2f} MN")
Diffraction Force:
def calculate_diffraction_coefficient(
diameter: float,
wavelength: float
) -> float:
"""
Estimate diffraction coefficient.
Diffraction important when D/λ > 0.2 (MacCamy-Fuchs)
Args:
diameter: Characteristic diameter
wavelength: Wave length
Returns:
Diffraction importance factor
"""
D_over_lambda = diameter / wavelength
if D_over_lambda < 0.1:
regime = "Morison (small body)"
elif D_over_lambda < 0.2:
regime = "Transition"
else:
regime = "Diffraction (large body)"
print(f"D/λ = {D_over_lambda:.3f} → {regime}")
return D_over_lambda
D = 58
T = 10
wavelength = 9.81 * T**2 / (2 * np.pi)
diffraction_coef = calculate_diffraction_coefficient(D, wavelength)
5. Hydrostatic Stiffness
Stiffness Matrix:
def calculate_hydrostatic_stiffness(
waterplane_area: float,
center_of_buoyancy: np.ndarray,
metacentric_height_long: float,
metacentric_height_trans: float,
displacement: float,
rho: float = 1025
) -> np.ndarray:
"""
Calculate 6x6 hydrostatic stiffness matrix.
Args:
waterplane_area: Waterplane area (m²)
center_of_buoyancy: [x, y, z] position (m)
metacentric_height_long: Longitudinal GM (m)
metacentric_height_trans: Transverse GM (m)
displacement: Vessel displacement (tonnes)
rho: Water density (kg/m³)
Returns:
6x6 hydrostatic stiffness matrix
"""
g = 9.81
mass = displacement * 1000
K = np.zeros((6, 6))
K[2, 2] = rho * g * waterplane_area
K[3, 3] = mass * g * metacentric_height_trans
K[4, 4] = mass * g * metacentric_height_long
K[2, 4] = -rho * g * waterplane_area * center_of_buoyancy[0]
K[4, 2] = K[2, 4]
K[2, 3] = -rho * g * waterplane_area * center_of_buoyancy[1]
K[3, 2] = K[2, 3]
return K
K_hydro = calculate_hydrostatic_stiffness(
waterplane_area=,
center_of_buoyancy=np.array([, , -]),
metacentric_height_long=,
metacentric_height_trans=,
displacement=
)
()
(K_hydro)
6. Wave Spectra and Irregular Seas
JONSWAP Spectrum:
def jonswap_spectrum(
frequencies: np.ndarray,
Hs: float,
Tp: float,
gamma: float = 3.3
) -> np.ndarray:
"""
Calculate JONSWAP wave spectrum.
S(f) = α g² (2π)^-4 f^-5 exp[-5/4(f/fp)^-4] γ^exp[-(f-fp)²/(2σ²fp²)]
Args:
frequencies: Frequency array (Hz)
Hs: Significant wave height (m)
Tp: Peak period (s)
gamma: Peak enhancement factor (default 3.3)
Returns:
Spectral density S(f) (m²/Hz)
"""
g = 9.81
fp = 1 / Tp
alpha = 5.0 / 16.0 * Hs**2 * fp**4 / g**2
sigma = np.where(frequencies <= fp, 0.07, 0.09)
S_PM = alpha * g**2 * (2*np.pi)**(-4) * frequencies**(-5) * \
np.exp(-5/4 * (frequencies / fp)**(-4))
gamma_factor = gamma ** np.exp(-(frequencies - fp)**2 / (2 * sigma**2 * fp**2))
S_JONSWAP = S_PM * gamma_factor
return S_JONSWAP
freq = np.linspace(0.01, 0.5, 500)
S = jonswap_spectrum(freq, Hs=8.5, Tp=12.0, gamma=3.3)
m0 = np.trapz(S, freq)
Hs_calculated = * np.sqrt(m0)
()
()
Response Spectrum:
def calculate_response_spectrum(
wave_spectrum: np.ndarray,
rao_amplitude: np.ndarray,
frequencies: np.ndarray
) -> tuple[np.ndarray, dict]:
"""
Calculate response spectrum from wave spectrum and RAO.
S_response(ω) = |RAO(ω)|² * S_wave(ω)
Args:
wave_spectrum: Wave spectral density
rao_amplitude: RAO amplitude (m/m)
frequencies: Frequency array
Returns:
(response_spectrum, statistics)
"""
S_response = rao_amplitude**2 * wave_spectrum
m0 = np.trapz(S_response, frequencies)
m2 = np.trapz(S_response * frequencies**2, frequencies)
stats = {
'variance': m0,
'std_dev': np.sqrt(m0),
'significant_amplitude': 2 * np.sqrt(m0),
'zero_crossing_period': 2 * np.pi * np.sqrt(m0 / m2)
}
return S_response, stats
freq = np.linspace(0.01, 0.5, 500)
S_wave = jonswap_spectrum(freq, Hs=8.5, Tp=12.0)
rao_heave = 1.2 / np.sqrt((1 - (2*np.pi*freq / 0.6)**2)**2 + (0.1 * 2*np.pi*freq / 0.6)**2)
S_heave, stats_heave = calculate_response_spectrum(S_wave, rao_heave, freq)
print(f"Significant heave amplitude: {stats_heave[]:f} m")
()
Practical Applications
Application 1: Complete RAO Analysis
def complete_rao_analysis(
bem_results_file: str,
wave_headings: np.ndarray = None,
output_dir: str = 'reports/rao_analysis'
) -> dict:
"""
Complete RAO analysis from BEM results.
Args:
bem_results_file: Path to BEM results (WAMIT .out or AQWA .lis)
wave_headings: Wave heading array (degrees)
output_dir: Output directory
Returns:
RAO results dictionary
"""
import pandas as pd
from pathlib import Path
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
if wave_headings is None:
wave_headings = np.arange(0, 360, 30)
frequencies = np.linspace(0.1, 2.0, 50)
periods = 2 * np.pi / frequencies
raos = {}
dof_names = ['Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw']
for heading in wave_headings:
for i, dof in enumerate(dof_names):
heading == :
dof == :
rao = * np.ones_like(frequencies)
dof == :
rao = / np.sqrt(( - (frequencies/)**)** + (*frequencies/)**)
dof == :
rao = * frequencies / ( + (frequencies/)**)
:
rao = np.zeros_like(frequencies)
:
rao = np.random.rand((frequencies)) *
raos[(heading, dof)] = rao
dof dof_names:
df_rao = pd.DataFrame({
: periods,
**{: raos[(h, dof)] h wave_headings}
})
df_rao.to_csv(output_path / , index=)
plotly.graph_objects go
fig = go.Figure()
dof [, , ]:
rao_at_peak = [raos[(h, dof)][] h wave_headings]
fig.add_trace(go.Scatterpolar(
r=rao_at_peak,
theta=wave_headings,
name=dof,
mode=
))
fig.update_layout(
title=,
polar=(radialaxis=(visible=))
)
fig.write_html(output_path / )
()
()
raos
rao_results = complete_rao_analysis(
bem_results_file=,
wave_headings=np.array([, , , , ])
)
Application 2: Motion Prediction in Irregular Seas
def predict_vessel_motions_irregular_seas(
Hs: float,
Tp: float,
heading: float,
rao_data: dict,
duration: float = 3600
) -> dict:
"""
Predict vessel motions in irregular seas.
Args:
Hs: Significant wave height (m)
Tp: Peak period (s)
heading: Wave heading (degrees)
rao_data: RAO dictionary from BEM analysis
duration: Simulation duration (s)
Returns:
Motion statistics
"""
freq_hz = np.linspace(0.01, 0.5, 500)
omega = 2 * np.pi * freq_hz
S_wave = jonswap_spectrum(freq_hz, Hs, Tp)
dof_names = ['Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw']
motion_stats = {}
for dof in dof_names:
rao_amplitude = np.interp(
omega,
np.linspace(0.1, 2.0, 50),
rao_data.get((heading, dof), np.zeros(50))
)
S_response, stats = calculate_response_spectrum(S_wave, rao_amplitude, freq_hz)
motion_stats[dof] = {
'std_dev': stats['std_dev'],
'significant_amplitude': stats['significant_amplitude'],
'max_expected': stats['significant_amplitude'] *
}
motion_stats
motion_predictions = predict_vessel_motions_irregular_seas(
Hs=,
Tp=,
heading=,
rao_data=rao_results,
duration=
)
()
dof, stats motion_predictions.items():
()
()
()
Application 3: Added Mass Convergence Check
def check_added_mass_convergence(
panel_counts: list,
added_mass_results: list
) -> dict:
"""
Check convergence of added mass with panel count.
Args:
panel_counts: List of panel counts
added_mass_results: List of 6x6 added mass matrices
Returns:
Convergence assessment
"""
import plotly.graph_objects as go
A33_values = [A[2, 2] for A in added_mass_results]
relative_changes = [
abs(A33_values[i] - A33_values[i-1]) / A33_values[i-1] * 100
for i in range(1, len(A33_values))
]
fig = go.Figure()
fig.add_trace(go.Scatter(
x=panel_counts,
y=A33_values,
name='A33 (Heave Added Mass)',
mode='lines+markers'
))
fig.update_layout(
title='Added Mass Convergence Study',
xaxis_title='Panel Count',
yaxis_title='A33 (tonnes)',
hovermode='x unified'
))
fig.write_html('reports/added_mass_convergence.html')
converged = relative_changes[-1] < 1.0 if relative_changes else False
return {
'converged': converged,
: A33_values[-],
: relative_changes[-] relative_changes ,
: panel_counts[-] converged
}
panel_counts = [, , , , ]
A_results = [
np.diag([, , , , , ]),
np.diag([, , , , , ]),
np.diag([, , , , , ]),
np.diag([, , , , , ]),
np.diag([, , , , , ])
]
convergence = check_added_mass_convergence(panel_counts, A_results)
()
()
Integration with Software
WAMIT Workflow
wamit_workflow:
step_1_geometry:
tool: "Rhino, GHS, or MultiSurf"
output: "geometry.gdf"
requirements:
- "Waterline at z=0"
- "Wetted surface only"
- "Right-hand coordinate system"
step_2_panel_mesh:
tool: "WAMIT-PGEN or Multisurf"
output: "vessel.gdf"
quality_checks:
- "Panel aspect ratio < 3:1"
- "Panel size < λ/6"
- "Use symmetry if applicable"
step_3_run_wamit:
input_files:
- "vessel.frc"
- "vessel.pot"
- "vessel.gdf"
output_files:
- "vessel.out"
- "vessel.1"
- "vessel.3"
step_4_post_processing:
tools:
- "WAMIT-VIEW"
- "Python (read .1, .3 files)"
AQWA Workflow
aqwa_workflow:
step_1_geometry:
tool: "ANSYS DesignModeler or SpaceClaim"
output: "geometry.scdoc"
step_2_mesh:
tool: "AQWA-GS (meshing)"
output: "vessel.anl"
settings:
element_size: "Auto or manual"
symmetry: "Use if applicable"
step_3_analysis:
solver: "AQWA-NAUT or AQWA-LINE"
analysis_types:
- "Hydrodynamic diffraction"
- "Response calculation"
step_4_results:
output: "vessel.LIS"
exports:
- "RAOs → CSV"
- "Added mass/damping → CSV"
- "OrcaFlex YML"
Resources
- WAMIT User Manual: https://www.wamit.com/
- AQWA Documentation: ANSYS Help System
- OrcaWave Manual: Orcina documentation
- DNV-RP-C205: Environmental Conditions and Environmental Loads
- DNV-RP-H103: Modelling and Analysis of Marine Operations
- Lloyd's Register: Guidance on wave loading
Use this skill for all hydrodynamic analysis in DigitalModel!