327 lines
10 KiB
ReStructuredText
327 lines
10 KiB
ReStructuredText
Stability Tutorial
|
||
==================
|
||
|
||
This tutorial covers GZ curve calculations for intact stability analysis.
|
||
|
||
Introduction
|
||
------------
|
||
|
||
The GZ (righting arm) curve shows how a vessel's stability changes with heel angle.
|
||
Positive GZ values indicate the vessel will return to upright.
|
||
|
||
Basic GZ Calculation
|
||
--------------------
|
||
|
||
.. code-block:: python
|
||
|
||
from navaltoolbox import Hull, Vessel, StabilityCalculator
|
||
|
||
# Load hull
|
||
hull = Hull("dtmb5415.stl")
|
||
vessel = Vessel(hull)
|
||
|
||
# Create stability calculator
|
||
calc = StabilityCalculator(vessel, water_density=1025.0)
|
||
|
||
# Define loading condition
|
||
# Note: This is the vessel mass EXCLUDING dynamic tank fluids.
|
||
# The calculator will add the mass of any active tanks to this value.
|
||
displacement = 8635000 # kg (8635 tonnes)
|
||
cog = (71.67, 0.0, 7.555) # LCG, TCG, VCG in meters (Base/Lightship COG)
|
||
|
||
# Heel angles to calculate
|
||
heels = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60]
|
||
|
||
# Calculate GZ curve
|
||
curve = calc.gz_curve(displacement, cog, heels)
|
||
|
||
# Print results
|
||
print("Heel (°) GZ (m)")
|
||
print("-" * 20)
|
||
for heel, gz in zip(curve.heels(), curve.values()):
|
||
print(f"{heel:6.1f} {gz:+.3f}")
|
||
|
||
Fixed Trim Option
|
||
-----------------
|
||
|
||
By default, the calculator finds the free-trim equilibrium for each heel angle. However, you can force a constant trim angle using the `fixed_trim` parameter:
|
||
|
||
.. code-block:: python
|
||
|
||
# Calculate GZ curve maintaining exactly 0.0 degrees of trim
|
||
fixed_trim_curve = calc.gz_curve(displacement, cog, heels, fixed_trim=0.0)
|
||
|
||
# For cross curves (KN)
|
||
kn_curves_fixed = calc.kn_curve(displacements, heels, fixed_trim=0.0)
|
||
|
||
Analyzing the Curve
|
||
-------------------
|
||
|
||
Extract key stability metrics:
|
||
|
||
.. code-block:: python
|
||
|
||
# Get GZ values
|
||
heels = curve.heels()
|
||
gz_values = curve.values()
|
||
|
||
# Find maximum GZ
|
||
max_gz = max(gz_values)
|
||
max_gz_idx = gz_values.index(max_gz)
|
||
max_gz_heel = heels[max_gz_idx]
|
||
|
||
print(f"Maximum GZ: {max_gz:.3f}m at {max_gz_heel:.1f}°")
|
||
|
||
# Check positive range
|
||
positive_range = [h for h, gz in zip(heels, gz_values) if gz > 0]
|
||
print(f"Positive GZ range: 0° to {max(positive_range):.0f}°")
|
||
|
||
# Read freeboard and draft at specific heels
|
||
points = curve.get_stability_points()
|
||
for pt in points:
|
||
if pt.freeboard is not None and pt.freeboard < 0.0:
|
||
print(f"Deck edge submerged at {pt.heel}°")
|
||
|
||
# Initial GM (from slope at 0°)
|
||
# GZ ≈ GM × sin(φ) for small angles
|
||
if len(gz_values) > 1 and heels[1] > 0:
|
||
gm_approx = gz_values[1] / math.sin(math.radians(heels[1]))
|
||
print(f"Approximate GM: {gm_approx:.2f}m")
|
||
|
||
Effect of VCG
|
||
-------------
|
||
|
||
VCG (vertical center of gravity) significantly affects stability:
|
||
|
||
.. code-block:: python
|
||
|
||
# Compare different VCG values
|
||
vcg_values = [6.5, 7.0, 7.5, 8.0, 8.5]
|
||
|
||
print("VCG (m) Max GZ (m) Max GZ Angle")
|
||
print("-" * 45)
|
||
|
||
for vcg in vcg_values:
|
||
cog = (71.67, 0.0, vcg)
|
||
curve = calc.gz_curve(displacement, cog, [0, 10, 20, 30, 40, 50])
|
||
|
||
gz_vals = curve.values()
|
||
max_gz = max(gz_vals)
|
||
max_idx = gz_vals.index(max_gz)
|
||
|
||
print(f"{vcg:6.1f} {max_gz:.3f} {curve.heels()[max_idx]:.0f}°")
|
||
|
||
DTMB 5415 Reference Values
|
||
--------------------------
|
||
|
||
NavalToolbox has been validated against reference data from Ariffin (2017).
|
||
|
||
+--------+-------------+---------------+
|
||
| Heel | Reference | NavalToolbox |
|
||
+========+=============+===============+
|
||
| 10° | 0.339 m | 0.333 m |
|
||
+--------+-------------+---------------+
|
||
| 20° | 0.674 m | 0.669 m |
|
||
+--------+-------------+---------------+
|
||
| 30° | 0.993 m | 0.982 m |
|
||
+--------+-------------+---------------+
|
||
| 40° | 1.077 m | 1.052 m |
|
||
+--------+-------------+---------------+
|
||
|
||
**Maximum error: 2.5cm**
|
||
|
||
Plotting Results
|
||
----------------
|
||
|
||
Visualize the GZ curve using matplotlib:
|
||
|
||
.. code-block:: python
|
||
|
||
import matplotlib.pyplot as plt
|
||
|
||
heels = curve.heels()
|
||
gz_values = curve.values()
|
||
|
||
plt.figure(figsize=(10, 6))
|
||
plt.plot(heels, gz_values, 'b-o', linewidth=2, markersize=6)
|
||
plt.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
|
||
plt.xlabel('Heel Angle (degrees)')
|
||
plt.ylabel('GZ (m)')
|
||
plt.title('Stability Curve (GZ)')
|
||
plt.grid(True, alpha=0.3)
|
||
plt.xlim(0, max(heels))
|
||
plt.show()
|
||
|
||
Including Tanks
|
||
---------------
|
||
|
||
Free surface effects reduce effective GM:
|
||
|
||
.. code-block:: python
|
||
|
||
from navaltoolbox import Tank
|
||
|
||
# Add a fuel tank
|
||
fuel_tank = Tank.from_box(
|
||
"Fuel Tank",
|
||
x_min=50, x_max=70,
|
||
y_min=-5, y_max=5,
|
||
z_min=0, z_max=4,
|
||
fluid_density=850.0
|
||
)
|
||
|
||
# Alternatively, load from file (STL/VTK):
|
||
# fuel_tank = Tank("fuel_tank.stl", fluid_density=850.0, name="FO_1P")
|
||
|
||
# Or create from hull intersection (e.g., double bottom):
|
||
# fuel_tank = Tank.from_box_hull_intersection(
|
||
# hull,
|
||
# x_min=20, x_max=50, y_min=-10, y_max=10, z_min=0, z_max=1.5,
|
||
# fluid_density=850.0, name="DB_1C"
|
||
# )
|
||
fuel_tank.fill_percent = 50 # Partial fill = free surface
|
||
fuel_tank.permeability = 0.95 # Account for internal structure
|
||
|
||
vessel.add_tank(fuel_tank)
|
||
|
||
# Free surface correction
|
||
fsm = fuel_tank.free_surface_moment_t
|
||
fs_correction = (fsm * fuel_tank.fluid_mass / fuel_tank.fill_volume) / displacement
|
||
|
||
print(f"Free surface moment: {fsm:.1f} m⁴")
|
||
print(f"GM reduction (virtual VCG rise): {fs_correction:.3f}m")
|
||
|
||
print(f"GM reduction (virtual VCG rise): {fs_correction:.3f}m")
|
||
|
||
# Note: Both HydrostaticsCalculator and StabilityCalculator apply this
|
||
# correction automatically if tanks are present in the vessel.
|
||
# The GZ curve computed above ALREADY includes this reduction.
|
||
# No manual adjustment of VCG is required.
|
||
|
||
Downflooding Points
|
||
-------------------
|
||
|
||
Downflooding points are critical openings (vents, hatches) through which water can enter the hull. They are used to calculate the downflooding angle (:math:`\theta_f`).
|
||
|
||
.. code-block:: python
|
||
|
||
from navaltoolbox import DownfloodingOpening, OpeningType
|
||
|
||
# Create opening from a point
|
||
er_vent = DownfloodingOpening.from_point(
|
||
"ER Vent Stbd",
|
||
position=(60.0, 8.0, 12.0),
|
||
opening_type=OpeningType.vent()
|
||
)
|
||
vessel.add_opening(er_vent)
|
||
|
||
# Or load from file
|
||
# openings = DownfloodingOpening.from_file("openings.dxf", OpeningType.hatch())
|
||
# for op in openings:
|
||
# vessel.add_opening(op)
|
||
|
||
# Check for flooding
|
||
# Access detailed point data for specific heel angles
|
||
points = curve.get_stability_points()
|
||
for pt in points:
|
||
if pt.is_flooding:
|
||
print(f"Flooding detected at {pt.heel}° through: {pt.flooded_openings}")
|
||
break
|
||
|
||
Wind Heeling
|
||
------------
|
||
|
||
Wind heeling moments are calculated based on the lateral projected area of the vessel's superstructure (silhouette).
|
||
|
||
Per IMO 2008 IS Code §2.3.2, the wind lever arm :math:`Z` is calculated as the exact vertical distance between the centroid of the emerged lateral area (above the waterline) and the centroid of the submerged lateral area (below the waterline).
|
||
|
||
.. code-block:: python
|
||
|
||
from navaltoolbox import Silhouette
|
||
|
||
# Load silhouette from DXF
|
||
# silhouette = Silhouette("superstructure.dxf")
|
||
|
||
# Or create manually
|
||
silhouette = Silhouette.from_points([
|
||
(0, 0), (100, 0), (100, 10), (80, 20), (20, 20), (0, 10)
|
||
], "Superstructure")
|
||
|
||
vessel.add_silhouette(silhouette)
|
||
|
||
# Calculate complete stability
|
||
result = calc.complete_stability(displacement, cog, heels)
|
||
|
||
if result.has_wind_data():
|
||
wd = result.wind_data
|
||
print(f"Wind Area: {wd.emerged_area:.1f} m²")
|
||
print(f"Wind Moment Lever: {wd.wind_lever_arm:.2f} m")
|
||
|
||
Cross Curves (KN)
|
||
-----------------
|
||
|
||
Cross curves of stability (KN curves) describe the geometric stability of the hull form independent of the center of gravity height. They are typically plotted as **KN vs Displacement** for fixed heel angles.
|
||
|
||
.. code-block:: python
|
||
|
||
import numpy as np
|
||
import matplotlib.pyplot as plt
|
||
|
||
# Define range of displacements
|
||
# Example: 80% to 120% of design displacement
|
||
base_disp = 8635000.0
|
||
displacements = np.linspace(base_disp * 0.8, base_disp * 1.2, 5).tolist()
|
||
|
||
# Fixed heel angles for cross curves (typically every 10°)
|
||
heels = [10, 20, 30, 40, 50, 60]
|
||
|
||
# Calculate KN curves for all displacements at once
|
||
# Note: Returns a list of StabilityCurve objects, one per displacement
|
||
kn_curves = calc.kn_curve(displacements, heels)
|
||
|
||
# Plotting Cross Curves (KN vs Displacement)
|
||
plt.figure(figsize=(10, 6))
|
||
|
||
# We want to plot one line per heel angle
|
||
# X-axis: Displacement
|
||
# Y-axis: KN value
|
||
|
||
# Extract data: matrix [displacement_idx][heel_idx]
|
||
kn_values = [[curve.values()[h_idx] for curve in kn_curves] for h_idx in range(len(heels))]
|
||
|
||
for h_idx, heel in enumerate(heels):
|
||
plt.plot(displacements, kn_values[h_idx], 'o-', label=f'{heel}°')
|
||
|
||
plt.xlabel('Displacement (kg)')
|
||
plt.ylabel('KN (m)')
|
||
plt.title('Cross Curves of Stability (KN)')
|
||
plt.legend(title="Heel Angle")
|
||
plt.grid(True, alpha=0.3)
|
||
plt.show()
|
||
|
||
.. tip::
|
||
The `kn_curve` method is much faster than looping over displacements manually because it can optimize geometry re-use internally.
|
||
|
||
Verification via Scripting
|
||
--------------------------
|
||
|
||
Once you have calculated stability, you can verify it against regulations using the built-in scripting engine.
|
||
|
||
.. code-block:: python
|
||
|
||
from navaltoolbox import CriteriaContext, ScriptEngine, plotting
|
||
|
||
# 1. Create context from result
|
||
# (Assuming 'result' is the output of complete_stability())
|
||
ctx = CriteriaContext.from_result(result, "My Vessel", "Departure")
|
||
|
||
# 2. Run verification script
|
||
engine = ScriptEngine()
|
||
# Assuming rule file is available
|
||
# criteria = engine.run_script_file("rules/is_code_2008_general.rhai", ctx)
|
||
|
||
# 3. Plot results
|
||
# plotting.plot_criteria_result(criteria)
|
||
|
||
For more details, see the :doc:`Scriptable Verification <../userguide/scripting>` guide.
|