| name | wave-theory |
| version | 1.0.0 |
| description | Ocean wave theory including wave spectra, statistics, irregular seas, and wave transformation for offshore engineering |
| author | workspace-hub |
| category | subject-matter-expert |
| tags | ["waves","wave-theory","spectra","jonswap","pierson-moskowitz","wave-statistics","irregular-seas"] |
| platforms | ["engineering"] |
Wave Theory SME Skill
Comprehensive ocean wave theory expertise including wave mechanics, spectral analysis, wave statistics, and irregular sea modeling for offshore engineering applications.
When to Use This Skill
Use wave theory knowledge when:
- Wave spectra - JONSWAP, Pierson-Moskowitz, scatter diagrams
- Wave statistics - Significant wave height, spectral parameters
- Irregular seas - Generate time series from spectra
- Wave kinematics - Particle velocities and accelerations
- Wave transformation - Shoaling, refraction, diffraction
- Extreme values - Design wave estimation
Core Knowledge Areas
1. Regular Wave Theory
Linear (Airy) Wave Theory:
import numpy as np
def airy_wave_properties(
H: float,
T: float,
d: float,
g: float = 9.81
) -> dict:
"""
Calculate Airy wave properties.
Valid for: H/L < 0.14, d/L > 0.5 (deep water) or d/L < 0.05 (shallow)
Args:
H: Wave height (m)
T: Wave period (s)
d: Water depth (m)
g: Gravity (m/s²)
Returns:
Wave properties dictionary
"""
omega = 2 * np.pi / T
from scipy.optimize import fsolve
def dispersion(k):
return omega**2 - g * k * np.tanh(k * d)
k0 = omega**2 / g
k = fsolve(dispersion, k0)[0]
L = 2 * np.pi / k
C = omega / k
n = 0.5 * (1 + 2*k*d / np.sinh(2*k*d))
Cg = n * C
if d / L > 0.5:
regime = "Deep water"
elif d / L < 0.05:
regime = "Shallow water"
else:
regime =
{
: H,
: T,
: d,
: L,
: k,
: omega,
: omega / (*np.pi),
: C,
: Cg,
: regime,
: d / L,
: H / L,
: H / L
}
wave = airy_wave_properties(H=, T=, d=)
()
()
()
()
()
Wave Kinematics:
def wave_particle_kinematics(
z: float,
H: float,
T: float,
d: float,
t: float = 0,
x: float = 0,
g: float = 9.81
) -> dict:
"""
Calculate wave particle velocities and accelerations.
Args:
z: Vertical position (0 at SWL, negative below)
H: Wave height (m)
T: Wave period (s)
d: Water depth (m)
t: Time (s)
x: Horizontal position (m)
g: Gravity (m/s²)
Returns:
Particle kinematics
"""
wave = airy_wave_properties(H, T, d, g)
k = wave['wave_number']
omega = wave['frequency_rad_s']
a = H / 2
cosh_kz_d = np.cosh(k * (z + d))
sinh_kz_d = np.sinh(k * (z + d))
cosh_kd = np.cosh(k * d)
sinh_kd = np.sinh(k * d)
phase = k * x - omega * t
u = (omega * a * cosh_kz_d / sinh_kd) * np.cos(phase)
w = (omega * a * sinh_kz_d / sinh_kd) * np.sin(phase)
ax = -(omega**2 * a * cosh_kz_d / sinh_kd) * np.sin(phase)
az = (omega**2 * a * sinh_kz_d / sinh_kd) * np.cos(phase)
p_dynamic = g * a * (cosh_kz_d / cosh_kd) * np.cos(phase)
return {
'horizontal_velocity': u,
'vertical_velocity': w,
'horizontal_acceleration': ax,
'vertical_acceleration': az,
'dynamic_pressure': p_dynamic,
: np.sqrt(u** + w**),
: np.sqrt(ax** + az**)
}
kinematics = wave_particle_kinematics(z=, H=, T=, d=, t=, x=)
()
()
()
()
2. Wave Spectra
JONSWAP Spectrum:
def jonswap_spectrum(
frequencies: np.ndarray,
Hs: float,
Tp: float,
gamma: float = 3.3,
alpha: float = None
) -> 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 (3.3 for North Sea)
alpha: Phillips constant (calculated if None)
Returns:
Spectral density S(f) (m²/Hz)
"""
g = 9.81
fp = 1 / Tp
if alpha is None:
alpha = 5.061 * Hs**2 / Tp**4 * (1 - 0.287 * np.log(gamma))
sigma = np.where(frequencies <= fp, 0.07, 0.09)
S_PM = alpha * g**2 * (2*np.pi)**(-4) * frequencies**(-5) * \
np.exp(-1.25 * (frequencies / fp)**(-4))
r = np.exp(-(frequencies - fp)**2 / (2 * sigma**2 * fp**2))
gamma_factor = gamma ** r
S = S_PM * gamma_factor
return S
freq = np.linspace(, , )
S = jonswap_spectrum(freq, Hs=, Tp=, gamma=)
m0 = np.trapz(S, freq)
Hs_calc = * np.sqrt(m0)
()
()
()
()
Pierson-Moskowitz Spectrum:
def pierson_moskowitz_spectrum(
frequencies: np.ndarray,
Hs: float,
Tp: float = None,
U19_5: float = None
) -> np.ndarray:
"""
Calculate Pierson-Moskowitz spectrum (fully developed sea).
Args:
frequencies: Frequency array (Hz)
Hs: Significant wave height (m)
Tp: Peak period (s) - optional
U19_5: Wind speed at 19.5m height (m/s) - optional
Returns:
Spectral density S(f) (m²/Hz)
"""
g = 9.81
if Tp is not None:
fp = 1 / Tp
elif U19_5 is not None:
fp = 0.877 * g / (2 * np.pi * U19_5)
else:
raise ValueError("Must provide either Tp or U19_5")
alpha = 0.0081
S = alpha * g**2 * (2*np.pi)**(-4) * frequencies**(-5) * \
np.exp(-1.25 * (frequencies / fp)**(-4))
return S
S_PM = pierson_moskowitz_spectrum(freq, Hs=8.5, Tp=12.0)
m0_PM = np.trapz(S_PM, freq)
Hs_PM = 4 * np.sqrt(m0_PM)
print(f"P-M Spectrum Hs: {Hs_PM:f} m")
3. Wave Statistics
Spectral Parameters:
def calculate_spectral_parameters(
S: np.ndarray,
frequencies: np.ndarray
) -> dict:
"""
Calculate spectral wave parameters.
Args:
S: Wave spectrum (m²/Hz)
frequencies: Frequency array (Hz)
Returns:
Spectral parameters
"""
m0 = np.trapz(S, frequencies)
m1 = np.trapz(S * frequencies, frequencies)
m2 = np.trapz(S * frequencies**2, frequencies)
m4 = np.trapz(S * frequencies**4, frequencies)
Hs = 4 * np.sqrt(m0)
Tm01 = m0 / m1
Tz = np.sqrt(m0 / m2)
peak_idx = np.argmax(S)
Tp = 1 / frequencies[peak_idx]
epsilon = np.sqrt(1 - m2**2 / (m0 * m4))
k_mean = 2 * np.pi / (9.81 * Tz**2 / (2*np.pi))
steepness = k_mean * Hs / 2
return {
'm0': m0,
'm1': m1,
'm2': m2,
'm4': m4,
'Hs': Hs,
'Tp': Tp,
'Tz': Tz,
'Tm01': Tm01,
'spectral_width': epsilon,
'steepness': steepness
}
params = calculate_spectral_parameters(S, freq)
print(f"Spectral Parameters:")
print(f" Hs: m")
()
()
()
Wave Height Distribution:
def rayleigh_distribution(
H: np.ndarray,
Hs: float
) -> np.ndarray:
"""
Rayleigh distribution for wave heights in irregular seas.
P(H) = probability that wave height exceeds H
Args:
H: Wave height array (m)
Hs: Significant wave height (m)
Returns:
Exceedance probability
"""
H_rms = Hs / np.sqrt(2)
P = np.exp(-(H / H_rms)**2)
return P
def significant_wave_statistics(Hs: float) -> dict:
"""
Calculate wave statistics from Hs using Rayleigh distribution.
Args:
Hs: Significant wave height (m)
Returns:
Wave statistics
"""
H_rms = Hs / np.sqrt(2)
H_mean = H_rms * np.sqrt(np.pi / 2)
H_1_10 = H_rms * np.sqrt(2 * np.log(10))
H_1_100 = H_rms * np.sqrt(2 * np.log(100))
H_max_1000 = H_rms * np.sqrt(2 * np.log(1000))
return {
'Hs': Hs,
'H_mean': H_mean,
'H_rms': H_rms,
'H_1_10': H_1_10,
'H_1_100': H_1_100,
'H_max_1000': H_max_1000
}
stats = significant_wave_statistics(Hs=8.5)
print(f"Wave Statistics (Hs = m):")
()
()
()
()
4. Time Series Generation
Generate Irregular Wave Time Series:
def generate_irregular_wave_time_series(
S: np.ndarray,
frequencies: np.ndarray,
duration: float,
dt: float,
random_seed: int = None
) -> tuple[np.ndarray, np.ndarray]:
"""
Generate irregular wave elevation time series from spectrum.
Args:
S: Wave spectrum (m²/Hz)
frequencies: Frequency array (Hz)
duration: Duration (s)
dt: Time step (s)
random_seed: Random seed for reproducibility
Returns:
(time, elevation) arrays
"""
if random_seed is not None:
np.random.seed(random_seed)
time = np.arange(0, duration, dt)
eta = np.zeros_like(time)
df = frequencies[1] - frequencies[0]
for i, f in enumerate(frequencies):
if S[i] > 0:
amplitude = np.sqrt(2 * S[i] * df)
phase = np.random.uniform(0, 2*np.pi)
omega = 2 * np.pi * f
eta += amplitude * np.cos(omega * time + phase)
return time, eta
t, elevation = generate_irregular_wave_time_series(
S, freq,
duration=3600,
dt=0.1,
random_seed=42
)
Hs_timeseries = * np.std(elevation)
()
()
()
()
5. Wave Scatter Diagrams
Create Wave Scatter Diagram:
def create_wave_scatter_diagram(
Hs_bins: np.ndarray,
Tp_bins: np.ndarray,
location_data: dict
) -> np.ndarray:
"""
Create wave scatter diagram (probability table).
Args:
Hs_bins: Hs bin edges (m)
Tp_bins: Tp bin edges (s)
location_data: Historical wave data or hindcast
Returns:
Probability matrix (sum = 1.0)
"""
n_Hs = len(Hs_bins) - 1
n_Tp = len(Tp_bins) - 1
scatter = np.zeros((n_Hs, n_Tp))
for i in range(n_Hs):
Hs_mid = (Hs_bins[i] + Hs_bins[i+1]) / 2
Tp_expected = 3.6 * np.sqrt(Hs_mid)
from scipy.stats import weibull_min
p_Hs = weibull_min.pdf(Hs_mid, c=2, scale=2.5)
from scipy.stats import norm
for j in range(n_Tp):
Tp_mid = (Tp_bins[j] + Tp_bins[j+1]) / 2
p_Tp_given_Hs = norm.pdf(Tp_mid, loc=Tp_expected, scale=1.5)
scatter[i, j] = p_Hs * p_Tp_given_Hs
scatter /= scatter.sum()
return scatter
Hs_bins = np.array([, , , , , , , , , , ])
Tp_bins = np.array([, , , , , , , ])
scatter = create_wave_scatter_diagram(Hs_bins, Tp_bins, {})
annual_hours = scatter *
()
()
()
6. Extreme Value Analysis
Design Wave from Return Period:
def calculate_extreme_wave_height(
return_period_years: float,
Hs_annual_max: np.ndarray = None,
distribution: str = 'weibull'
) -> dict:
"""
Calculate design wave height for given return period.
Args:
return_period_years: Return period (years)
Hs_annual_max: Array of annual maximum Hs values
distribution: 'weibull' or 'gumbel'
Returns:
Extreme wave height statistics
"""
from scipy.stats import weibull_min, gumbel_r
if Hs_annual_max is None:
np.random.seed(42)
Hs_annual_max = weibull_min.rvs(c=2, scale=10, size=25)
if distribution == 'weibull':
params = weibull_min.fit(Hs_annual_max)
c, loc, scale = params
dist = weibull_min(c, loc, scale)
elif distribution == 'gumbel':
loc, scale = gumbel_r.fit(Hs_annual_max)
dist = gumbel_r(loc, scale)
else:
raise ValueError("Unknown distribution")
exceedance_prob = 1 / return_period_years
Hs_extreme = dist.ppf(1 - exceedance_prob)
Hs_lower = dist.ppf(1 - exceedance_prob - 0.1)
Hs_upper = dist.ppf(1 - exceedance_prob + 0.1)
return {
'return_period_years': return_period_years,
: Hs_extreme,
: Hs_lower,
: Hs_upper,
: distribution,
: exceedance_prob
}
extreme_100yr = calculate_extreme_wave_height(
return_period_years=,
distribution=
)
()
()
()
Complete Examples
Example 1: Complete Wave Analysis
def complete_wave_analysis(
Hs: float,
Tp: float,
depth: float,
duration: float = 3600,
output_dir: str = 'reports/wave_analysis'
) -> dict:
"""
Complete wave analysis: spectrum, time series, statistics.
Args:
Hs: Significant wave height (m)
Tp: Peak period (s)
depth: Water depth (m)
duration: Time series duration (s)
output_dir: Output directory
Returns:
Complete analysis results
"""
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from pathlib import Path
output_path = Path(output_dir)
output_path.mkdir(parents=True, exist_ok=True)
freq = np.linspace(0.01, 0.5, 500)
S = jonswap_spectrum(freq, Hs, Tp)
params = calculate_spectral_parameters(S, freq)
t, eta = generate_irregular_wave_time_series(S, freq, duration, dt=0.1)
wave_stats = significant_wave_statistics(Hs)
regular_wave = airy_wave_properties(Hs, Tp, depth)
fig = make_subplots(
rows=2, cols=2,
subplot_titles=(
'JONSWAP Spectrum',
'Wave Elevation Time Series',
'Wave Height Distribution',
'Wave Steepness'
)
)
fig.add_trace(
go.Scatter(x=freq, y=S, name=, line=(color=)),
row=, col=
)
t_plot = t[:]
eta_plot = eta[:]
fig.add_trace(
go.Scatter(x=t_plot, y=eta_plot, name=, line=(width=)),
row=, col=
)
H_array = np.linspace(, Hs*, )
P_exceedance = rayleigh_distribution(H_array, Hs)
fig.add_trace(
go.Scatter(
x=H_array, y=P_exceedance,
name=,
line=(color=)
),
row=, col=
)
steepness_freq = (*np.pi*freq)** / * np.sqrt(S)
fig.add_trace(
go.Scatter(x=freq, y=steepness_freq, name=),
row=, col=
)
fig.update_layout(height=, showlegend=, title_text=)
fig.write_html(output_path / )
summary = {
: {
: Hs,
: Tp,
: depth
},
: params,
: wave_stats,
: regular_wave,
: {
: duration,
: ,
: (t)
}
}
json
(output_path / , ) f:
json.dump(summary, f, indent=, default=)
()
()
summary
analysis = complete_wave_analysis(
Hs=,
Tp=,
depth=,
duration=
)
Resources
- Shore Protection Manual: US Army Corps of Engineers
- Ocean Waves and Oscillating Systems: J. Falnes
- Water Wave Mechanics for Engineers and Scientists: R.G. Dean & R.A. Dalrymple
- DNV-RP-C205: Environmental Conditions and Environmental Loads
- ISO 19901-1: Metocean design and operating considerations
Use this skill for all wave analysis in DigitalModel!