| name | fatigue-analysis |
| description | Perform fatigue analysis using S-N curves and damage accumulation methods. Supports 221 S-N curves from 17 international standards (DNV, API, BS, ABS, etc.) for marine and offshore structural components. |
| updated | 2026-01-07 |
Fatigue Analysis Skill
Perform fatigue analysis for marine and offshore structural components using industry-standard S-N curves and damage accumulation methods.
Version Metadata
version: 1.0.0
python_min_version: '3.10'
dependencies:
signal-analysis: '>=1.0.0,<2.0.0'
structural-analysis: '>=1.0.0,<2.0.0'
compatibility:
tested_python:
- '3.10'
- '3.11'
- '3.12'
- '3.13'
os:
- Windows
- Linux
- macOS
Changelog
[1.0.0] - 2026-01-07
Added:
- Initial version metadata and dependency management
- Semantic versioning support
- Compatibility information for Python 3.10-3.13
Changed:
- Enhanced skill documentation structure
When to Use
- Fatigue life assessment of welded joints
- S-N curve selection from international standards
- Stress concentration factor application
- Damage accumulation using Palmgren-Miner rule
- Fatigue limit evaluation
- Comparison of different standards
- Generating fatigue analysis reports
Supported Standards
Available S-N Curves (221 total)
| Standard | Curves | Description |
|---|
| DNV-RP-C203 | 45+ | Offshore steel structures |
| API RP 2A | 15+ | Offshore platforms |
| BS 7608 | 20+ | Fatigue design of steel structures |
| ABS | 18+ | Marine vessel structures |
| Eurocode 3 | 14 | Steel structures |
| IIW | 12+ | Welded joints |
| ASME | 10+ | Pressure vessels |
| AWS D1.1 | 8+ | Structural welding |
| AISC | 5 | Steel construction |
| ISO 19902 | 12+ | Fixed offshore structures |
Core Concepts
S-N Curve Equation
The basic S-N relationship:
N = a / S^m
Where:
- N = Number of cycles to failure
- S = Stress range
- a = Intercept parameter (log scale)
- m = Slope parameter (typically 3-5 for steel)
Damage Accumulation (Miner's Rule)
D = Σ (ni / Ni)
Where:
- D = Accumulated damage (failure at D ≥ 1.0)
- ni = Number of cycles at stress level i
- Ni = Cycles to failure at stress level i (from S-N curve)
Implementation Pattern
S-N Curve Database
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
import numpy as np
import logging
logger = logging.getLogger(__name__)
@dataclass
class SNCurve:
"""S-N curve parameters."""
name: str
standard: str
category: str
log_a: float
m: float
log_a_2: Optional[float] = None
m_2: Optional[float] = None
n_transition: float = 1e7
fatigue_limit: Optional[float] = None
t_ref: float = 25.0
k: float = 0.0
environment: str = "air"
def get_cycles_to_failure(
self,
stress_range: ,
thickness: =
) -> :
thickness thickness > .t_ref:
stress_range = stress_range * (thickness / .t_ref) ** .k
.fatigue_limit stress_range < .fatigue_limit:
()
log_s = np.log10(stress_range)
log_n = .log_a - .m * log_s
.log_a_2 .m_2:
n_first = ** log_n
n_first > .n_transition:
log_n = .log_a_2 - .m_2 * log_s
** log_n
:
():
.curves: [, SNCurve] = {}
._load_standard_curves()
():
dnv_curves = [
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
(, , , , , ),
]
name, log_a, m, log_a_2, m_2, category dnv_curves:
curve_id =
.curves[curve_id] = SNCurve(
name=name,
standard=,
category=category,
log_a=log_a,
m=m,
log_a_2=log_a_2,
m_2=m_2,
n_transition=,
t_ref=,
k=,
environment=
)
api_curves = [
(, , , ),
(, , , ),
]
name, log_a, m, category api_curves:
curve_id =
.curves[curve_id] = SNCurve(
name=name,
standard=,
category=category,
log_a=log_a,
m=m,
environment=
)
() -> SNCurve:
curve_id .curves:
available = .join(.curves.keys())
ValueError()
.curves[curve_id]
() -> []:
standard:
[k k, v .curves.items() v.standard == standard]
(.curves.keys())
() -> [SNCurve]:
[v v .curves.values() category.lower() v.category.lower()]
Fatigue Calculator
@dataclass
class StressBlock:
"""Stress range block for fatigue analysis."""
stress_range: float
cycles: int
@dataclass
class FatigueResult:
"""Results of fatigue analysis."""
total_damage: float
fatigue_life_years: float
design_life_years: float
utilization: float
damage_by_block: List[float]
curve_used: str
passes: bool
def summary(self) -> str:
"""Generate summary string."""
status = "PASS" if self.passes else "FAIL"
return (
f"Fatigue Analysis Result: {status}\n"
f" S-N Curve: {self.curve_used}\n"
f" Total Damage: {self.total_damage:.4f}\n"
f" Fatigue Life: {self.fatigue_life_years:.1f} years\n"
f" Design Life: {self.design_life_years:.1f} years\n"
f" Utilization: {self.utilization:.1%}"
)
class :
():
.db = sn_database SNCurveDatabase()
() -> FatigueResult:
curve = .db.get_curve(curve_id)
total_damage =
damage_by_block = []
block stress_blocks:
effective_stress = block.stress_range * scf
n_failure = curve.get_cycles_to_failure(effective_stress, thickness)
n_failure == ():
block_damage =
:
block_damage = block.cycles / n_failure
damage_by_block.append(block_damage)
total_damage += block_damage
total_damage *= dff
total_damage > :
fatigue_life_years = design_life_years / total_damage
:
fatigue_life_years = ()
utilization = total_damage
FatigueResult(
total_damage=total_damage,
fatigue_life_years=fatigue_life_years,
design_life_years=design_life_years,
utilization=utilization,
damage_by_block=damage_by_block,
curve_used=curve_id,
passes=total_damage <=
)
() -> [, FatigueResult]:
results = {}
curve_id curve_ids:
:
results[curve_id] = .calculate_damage(
curve_id=curve_id,
stress_blocks=stress_blocks,
design_life_years=design_life_years,
scf=scf
)
ValueError e:
logger.warning()
results
Stress Spectrum Generator
def generate_weibull_spectrum(
n_blocks: int = 20,
max_stress: float = 100.0,
shape: float = 1.0,
total_cycles: int = 1e7
) -> List[StressBlock]:
"""
Generate stress spectrum using Weibull distribution.
Args:
n_blocks: Number of stress blocks
max_stress: Maximum stress range (MPa)
shape: Weibull shape parameter
total_cycles: Total number of cycles
Returns:
List of StressBlock objects
"""
blocks = []
stress_levels = np.linspace(max_stress, max_stress * 0.1, n_blocks)
scale = max_stress / (-np.log(1 - 0.632)) ** (1/shape)
probabilities = np.exp(-(stress_levels / scale) ** shape)
prob_diff = np.diff(np.concatenate([[0], probabilities, [1]]))
cycles_per_block = (prob_diff[:-1] * total_cycles).astype(int)
for stress, cycles in zip(stress_levels, cycles_per_block):
if cycles > 0:
blocks.append(StressBlock(stress_range=stress, cycles=cycles))
return blocks
def generate_rainflow_spectrum(
time_history: np.ndarray,
bin_edges: np.ndarray = None
) -> List[StressBlock]:
"""
Generate stress spectrum from time history using rainflow counting.
Args:
time_history: Array of stress values over time
bin_edges: Edges for binning stress ranges
Returns:
List of StressBlock objects
"""
bin_edges :
max_range = np.ptp(time_history)
bin_edges = np.linspace(, max_range, )
reversals = []
i (, (time_history) - ):
((time_history[i] > time_history[i-]
time_history[i] > time_history[i+])
(time_history[i] < time_history[i-]
time_history[i] < time_history[i+])):
reversals.append(time_history[i])
ranges = []
i =
i < (reversals) - :
s1 = (reversals[i+] - reversals[i])
s2 = (reversals[i+] - reversals[i+])
s3 = (reversals[i+] - reversals[i+])
s2 <= s1 s2 <= s3:
ranges.append(s2)
reversals[i+:i+]
:
i +=
counts, _ = np.histogram(ranges, bins=bin_edges)
bin_centers = (bin_edges[:-] + bin_edges[:]) /
blocks = []
stress, cycles (bin_centers, counts):
cycles > :
blocks.append(StressBlock(stress_range=stress, cycles=(cycles)))
blocks
YAML Configuration
analysis:
name: "Riser Girth Weld Fatigue"
design_life_years: 25
design_fatigue_factor: 3.0
joint:
type: "girth_weld"
sn_curve: "DNV_D"
thickness_mm: 32.0
scf: 1.25
environment: "seawater_cp"
stress_spectrum:
type: "weibull"
max_stress_mpa: 150.0
shape_parameter: 1.0
total_cycles: 1.0e8
output:
report_path: "reports/fatigue_analysis.html"
include_comparison: true
comparison_curves:
- "DNV_D"
- "DNV_E"
- "API_X"
Usage Examples
Basic Analysis
from fatigue_analysis import FatigueCalculator, StressBlock
calc = FatigueCalculator()
blocks = [
StressBlock(stress_range=150.0, cycles=1000),
StressBlock(stress_range=100.0, cycles=10000),
StressBlock(stress_range=75.0, cycles=50000),
StressBlock(stress_range=50.0, cycles=200000),
StressBlock(stress_range=25.0, cycles=1000000),
]
result = calc.calculate_damage(
curve_id="DNV_D",
stress_blocks=blocks,
design_life_years=25,
scf=1.2,
dff=3.0
)
print(result.summary())
Compare Standards
calc = FatigueCalculator()
results = calc.compare_standards(
curve_ids=["DNV_D", "DNV_E", "API_X"],
stress_blocks=blocks,
design_life_years=25,
scf=1.2
)
for curve_id, result in results.items():
print(f"{curve_id}: Damage = {result.total_damage:.4f}")
Generate Report
from fatigue_analysis import FatigueCalculator
from engineering_report_generator import generate_report
import pandas as pd
calc = FatigueCalculator()
result = calc.calculate_damage(...)
df = pd.DataFrame({
'Block': range(1, len(blocks) + 1),
'Stress Range': [b.stress_range for b in blocks],
'Cycles': [b.cycles for b in blocks],
'Damage': result.damage_by_block
})
generate_report(
df=df,
output_path='reports/fatigue_report.html',
title='Fatigue Analysis Report',
sections={
'summary': f'Total Damage: {result.total_damage:.4f}',
'charts': [
{'type': 'bar', 'x': 'Block', 'y': 'Damage', 'title': 'Damage by Block'},
{'type': 'scatter', 'x': 'Stress Range', 'y': 'Cycles', 'title': 'S-N Spectrum'}
]
}
)
Best Practices
S-N Curve Selection
- Select curves based on joint type and fabrication quality
- Consider environment (air, seawater, cathodic protection)
- Apply appropriate thickness corrections
- Use design curves (mean minus 2 standard deviations)
Stress Analysis
- Include all stress concentration effects
- Consider mean stress correction if needed
- Account for multiaxial stresses
- Include weld misalignment effects
Safety Factors
- DNV recommends DFF of 1.0 to 10.0 depending on consequence
- API uses single safety factor approach
- Consider inspection accessibility
- Account for consequences of failure
Reporting
- Document S-N curve selection rationale
- Include stress spectrum derivation
- Show damage distribution
- Compare with alternative standards
Related Skills