hydrodynamic-analysis

Hydrodynamic Analysis SME Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "hydrodynamic-analysis" with this command: npx skills add vamseeachanta/workspace-hub/vamseeachanta-workspace-hub-hydrodynamic-analysis

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

Radiation Problem:

- Forced oscillation in calm water

- Calculates added mass and damping

- 6 DOFs → 6 radiation potentials

Diffraction Problem:

- Fixed body in waves

- Calculates wave excitation forces

- Different wave headings analyzed

Panel Mesh Quality:

mesh_requirements: panel_size: general: "< λ/6" # Lambda = wavelength critical_areas: "< λ/10" # Bow, stern, sharp edges

aspect_ratio: maximum: 3.0 preferred: 1.5

panel_count: minimum: 2000 # Small vessels typical: 5000-10000 # FPSOs large: 20000+ # Complex geometries

symmetry: use_if_possible: true # Reduces computational cost by 50% check: "Ensure port-starboard symmetry"

  1. 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)
"""
# Dynamic stiffness matrix (complex)
K_dynamic = (
    -omega**2 * (mass_matrix + added_mass) +
    1j * omega * damping +
    stiffness
)

# Solve for RAO
rao_complex = np.linalg.solve(K_dynamic, wave_excitation)

return rao_complex

Example: Calculate heave RAO

omega = 2 * np.pi / 10 # T = 10s M = np.diag([150000, 150000, 150000, 1e7, 1e7, 5e6]) # Mass matrix A = np.diag([15000, 15000, 50000, 1e6, 1e6, 5e5]) # Added mass B = np.diag([50000, 50000, 100000, 5e5, 5e5, 2e5]) # Damping K = np.diag([0, 0, 3000, 0, 0, 0]) # Hydrostatic stiffness

Wave excitation (heave dominant)

F_wave = np.array([100000, 0, 500000, 0, 1e6, 0]) + 0j

rao = calculate_rao_from_hydrodynamic_coefficients(omega, M, A, B, K, F_wave)

Heave RAO amplitude

heave_rao_amplitude = np.abs(rao[2]) heave_rao_phase = np.angle(rao[2], deg=True)

print(f"Heave RAO: {heave_rao_amplitude:.3f} m/m at {heave_rao_phase:.1f}°")

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
"""
# Find peak
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  # Typical threshold
}

Example

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")

  1. 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

# Interpolate each element
coefficient_interp = np.zeros((6, 6))

for i in range(6):
    for j in range(6):
        # Extract time series for this coefficient
        coef_series = coefficient_data[:, i, j]

        # Create interpolator
        interpolator = interp1d(
            omega_data,
            coef_series,
            kind='cubic',
            fill_value='extrapolate'
        )

        # Interpolate
        coefficient_interp[i, j] = interpolator(omega_target)

return coefficient_interp

Example usage

omega_data = np.array([0.1, 0.5, 1.0, 1.5, 2.0]) # rad/s added_mass_data = np.random.rand(5, 6, 6) * 10000 # Sample data

Interpolate at T = 8s (omega = 0.785 rad/s)

A_interp = interpolate_hydrodynamic_coefficients( omega_target=2*np.pi/8, omega_data=omega_data, coefficient_data=added_mass_data )

print(f"Added mass at T=8s:") print(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
"""
# Use highest frequency values and extrapolate
# Typically: fit A(ω) = A_inf + C/ω²

A_inf = np.zeros((6, 6))

for i in range(6):
    for j in range(6):
        # Take average of highest 3 frequencies
        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

# Wave number (deep water approximation)
k = omega**2 / g

# Froude-Krylov force
F_FK = rho * g * wave_amplitude * waterplane_area * np.exp(k * center_of_buoyancy_depth)

return F_FK

Example

F_FK = calculate_froude_krylov_force( wave_amplitude=2.0, # 2m amplitude (Hs = 4m) omega=2*np.pi/10, # 10s period waterplane_area=15000, # m² center_of_buoyancy_depth=-10 # 10m below waterline )

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 &#x3C; 0.1:
    regime = "Morison (small body)"
elif D_over_lambda &#x3C; 0.2:
    regime = "Transition"
else:
    regime = "Diffraction (large body)"

print(f"D/λ = {D_over_lambda:.3f} → {regime}")

return D_over_lambda

Example: FPSO hull

D = 58 # Beam = 58m T = 10 # Wave period wavelength = 9.81 * T**2 / (2 * np.pi) # Deep water

diffraction_coef = calculate_diffraction_coefficient(D, wavelength)

  1. 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  # kg

K = np.zeros((6, 6))

# Heave stiffness: K_33 = ρ g A_wp
K[2, 2] = rho * g * waterplane_area

# Roll stiffness: K_44 = ρ g ∇ GM_T
K[3, 3] = mass * g * metacentric_height_trans

# Pitch stiffness: K_55 = ρ g ∇ GM_L
K[4, 4] = mass * g * metacentric_height_long

# Heave-pitch coupling
K[2, 4] = -rho * g * waterplane_area * center_of_buoyancy[0]
K[4, 2] = K[2, 4]

# Heave-roll coupling
K[2, 3] = -rho * g * waterplane_area * center_of_buoyancy[1]
K[3, 2] = K[2, 3]

return K

Example: FPSO hydrostatic stiffness

K_hydro = calculate_hydrostatic_stiffness( waterplane_area=15000, # m² center_of_buoyancy=np.array([160, 0, -10]), # m metacentric_height_long=5.0, # m metacentric_height_trans=3.0, # m displacement=150000 # tonnes )

print("Hydrostatic Stiffness Matrix:") print(K_hydro)

  1. 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  # Peak frequency

# Phillips constant
alpha = 5.0 / 16.0 * Hs**2 * fp**4 / g**2

# Spectral width parameter
sigma = np.where(frequencies &#x3C;= fp, 0.07, 0.09)

# JONSWAP spectrum
S_PM = alpha * g**2 * (2*np.pi)**(-4) * frequencies**(-5) * \
       np.exp(-5/4 * (frequencies / fp)**(-4))

# Peak enhancement
gamma_factor = gamma ** np.exp(-(frequencies - fp)**2 / (2 * sigma**2 * fp**2))

S_JONSWAP = S_PM * gamma_factor

return S_JONSWAP

Example: Generate JONSWAP spectrum

freq = np.linspace(0.01, 0.5, 500) # Hz S = jonswap_spectrum(freq, Hs=8.5, Tp=12.0, gamma=3.3)

Check Hs from spectrum

m0 = np.trapz(S, freq) # Zero-order moment Hs_calculated = 4 * np.sqrt(m0)

print(f"Input Hs: 8.5 m") print(f"Calculated Hs from spectrum: {Hs_calculated:.2f} m")

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)
"""
# Response spectrum
S_response = rao_amplitude**2 * wave_spectrum

# Calculate statistics
m0 = np.trapz(S_response, frequencies)  # Variance
m2 = np.trapz(S_response * frequencies**2, frequencies)

# Response statistics
stats = {
    'variance': m0,
    'std_dev': np.sqrt(m0),
    'significant_amplitude': 2 * np.sqrt(m0),  # ≈ H_1/3 for motions
    'zero_crossing_period': 2 * np.pi * np.sqrt(m0 / m2)
}

return S_response, stats

Example

freq = np.linspace(0.01, 0.5, 500) S_wave = jonswap_spectrum(freq, Hs=8.5, Tp=12.0)

Sample heave RAO

rao_heave = 1.2 / np.sqrt((1 - (2np.pifreq / 0.6)**2)**2 + (0.1 * 2np.pifreq / 0.6)**2)

S_heave, stats_heave = calculate_response_spectrum(S_wave, rao_heave, freq)

print(f"Significant heave amplitude: {stats_heave['significant_amplitude']:.2f} m") print(f"Heave zero-crossing period: {stats_heave['zero_crossing_period']:.2f} s")

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)

# Load BEM results (example format)
# In reality, parse WAMIT .out or AQWA results
frequencies = np.linspace(0.1, 2.0, 50)  # rad/s
periods = 2 * np.pi / frequencies

# Sample RAO data (6 DOFs x n_frequencies x n_headings)
raos = {}
dof_names = ['Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw']

for heading in wave_headings:
    for i, dof in enumerate(dof_names):
        # Simplified RAO calculation
        # In practice, read from BEM output
        if heading == 0:  # Head seas
            if dof == 'Surge':
                rao = 0.8 * np.ones_like(frequencies)
            elif dof == 'Heave':
                rao = 1.2 / np.sqrt((1 - (frequencies/0.6)**2)**2 + (0.1*frequencies/0.6)**2)
            elif dof == 'Pitch':
                rao = 0.05 * frequencies / (1 + (frequencies/0.6)**2)
            else:
                rao = np.zeros_like(frequencies)
        else:
            # Simplified for other headings
            rao = np.random.rand(len(frequencies)) * 0.5

        raos[(heading, dof)] = rao

# Export RAOs to CSV
for dof in dof_names:
    df_rao = pd.DataFrame({
        'Period_s': periods,
        **{f'Heading_{h}deg': raos[(h, dof)] for h in wave_headings}
    })

    df_rao.to_csv(output_path / f'RAO_{dof}.csv', index=False)

# Create polar plot
import plotly.graph_objects as go

fig = go.Figure()

for dof in ['Surge', 'Heave', 'Pitch']:
    rao_at_peak = [raos[(h, dof)][25] for h in wave_headings]  # At T=10s

    fig.add_trace(go.Scatterpolar(
        r=rao_at_peak,
        theta=wave_headings,
        name=dof,
        mode='lines+markers'
    ))

fig.update_layout(
    title='RAO Polar Plot (T = 10s)',
    polar=dict(radialaxis=dict(visible=True))
)

fig.write_html(output_path / 'RAO_polar.html')

print(f"✓ RAO analysis complete")
print(f"  Output: {output_dir}")

return raos

Usage

rao_results = complete_rao_analysis( bem_results_file='data/processed/wamit_results.out', wave_headings=np.array([0, 45, 90, 135, 180]) )

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
"""
# Frequency array
freq_hz = np.linspace(0.01, 0.5, 500)
omega = 2 * np.pi * freq_hz

# Wave spectrum
S_wave = jonswap_spectrum(freq_hz, Hs, Tp)

# Calculate response spectra
dof_names = ['Surge', 'Sway', 'Heave', 'Roll', 'Pitch', 'Yaw']
motion_stats = {}

for dof in dof_names:
    # Get RAO for this heading and DOF
    # Simplified: use constant RAO
    rao_amplitude = np.interp(
        omega,
        np.linspace(0.1, 2.0, 50),
        rao_data.get((heading, dof), np.zeros(50))
    )

    # Response spectrum
    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'] * 1.86  # Rayleigh distribution
    }

return motion_stats

Example

motion_predictions = predict_vessel_motions_irregular_seas( Hs=8.5, Tp=12.0, heading=0, # Head seas rao_data=rao_results, duration=10800 # 3 hours )

print("Predicted Motion Statistics:") for dof, stats in motion_predictions.items(): print(f"{dof}:") print(f" Significant amplitude: {stats['significant_amplitude']:.2f}") print(f" Max expected (3hr): {stats['max_expected']:.2f}")

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

# Check heave added mass convergence
A33_values = [A[2, 2] for A in added_mass_results]

# Calculate relative change
relative_changes = [
    abs(A33_values[i] - A33_values[i-1]) / A33_values[i-1] * 100
    for i in range(1, len(A33_values))
]

# Plot convergence
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')

# Convergence criteria: &#x3C; 1% change
converged = relative_changes[-1] &#x3C; 1.0 if relative_changes else False

return {
    'converged': converged,
    'final_value': A33_values[-1],
    'relative_change_percent': relative_changes[-1] if relative_changes else 0,
    'recommended_panels': panel_counts[-1] if converged else 'Increase further'
}

Example

panel_counts = [1000, 2000, 5000, 10000, 15000] A_results = [ np.diag([15000, 15000, 45000, 1e6, 1e6, 5e5]), np.diag([15000, 15000, 48000, 1e6, 1e6, 5e5]), np.diag([15000, 15000, 49500, 1e6, 1e6, 5e5]), np.diag([15000, 15000, 50000, 1e6, 1e6, 5e5]), np.diag([15000, 15000, 50100, 1e6, 1e6, 5e5]) ]

convergence = check_added_mass_convergence(panel_counts, A_results) print(f"Converged: {convergence['converged']}") print(f"Recommended panels: {convergence['recommended_panels']}")

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" # Force control - "vessel.pot" # Potential control - "vessel.gdf" # Geometry output_files: - "vessel.out" # Main output - "vessel.1" # Added mass/damping - "vessel.3" # Wave excitation

step_4_post_processing: tools: - "WAMIT-VIEW" - "Python (read .1, .3 files)" - "OrcaFlex (import RAOs)"

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!

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Research

numpy-numerical-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
Research

knowledge-base-builder

No summary provided by upstream source.

Repository SourceNeeds Review
Research

mooring-analysis

No summary provided by upstream source.

Repository SourceNeeds Review
Research

core-researcher

No summary provided by upstream source.

Repository SourceNeeds Review