Tank Modeling Tutorial

This tutorial demonstrates how to model fluid tanks in pynavaltoolbox, including creating different tank types, managing fill levels, and incorporating free surface effects into stability calculations.

Introduction

Tanks are essential for modeling ballast, fuel, and cargo liquids in vessels. pynavaltoolbox provides tools to:

  • Define tank geometries from files or dimensions

  • Calculate tank volumes, mass, and center of gravity

  • Compute free surface moments for stability corrections

  • Integrate tanks with hydrostatic and stability calculations

Creating Tanks

From Box Dimensions

The simplest way to create a tank is from rectangular dimensions:

from pynavaltoolbox.tanks import Tank

# Create a fuel tank
fuel_tank = Tank.from_box(
    x_min=30.0, x_max=45.0,   # 15m length
    y_min=-4.0, y_max=4.0,    # 8m width
    z_min=2.0, z_max=8.0,     # 6m height
    fluid_density=850.0,      # Heavy fuel oil (kg/m³)
    name="FuelTank_P"
)

print(f"Tank volume: {fuel_tank.total_volume:.1f} m³")
# Output: Tank volume: 720.0 m³

From Hull Intersection

For tanks that follow the ship’s hull shape (like double bottoms):

from pynavaltoolbox import Hull
from pynavaltoolbox.tanks import Tank

# Load hull geometry
hull = Hull("ship_hull.stl")

# Create double-bottom tank
db_tank = Tank.from_box_hull_intersection(
    hull,
    x_min=40.0, x_max=80.0,   # Midship section
    y_min=-12.0, y_max=12.0,  # Full beam
    z_min=0.0, z_max=1.8,     # Double bottom height
    fluid_density=1025.0,     # Seawater ballast
    name="DoubleBottom_Center"
)

print(f"Tank volume: {db_tank.total_volume:.1f} m³")
# Volume follows hull's curved bottom

Managing Fill Levels

Fill levels can be set using different units:

tank = Tank.from_box(
    x_min=0.0, x_max=10.0,
    y_min=-5.0, y_max=5.0,
    z_min=0.0, z_max=10.0,
    fluid_density=1000.0,
    name="TestTank"
)
# Total volume: 1000 m³

# Set by percentage
tank.set_fill_level(75, unit='percent')
print(f"Fill: {tank.fill_percent}% = {tank.fill_volume:.0f} m³")
# Output: Fill: 75.0% = 750 m³

# Set by fraction
tank.set_fill_level(0.5, unit='fraction')
print(f"Fill: {tank.fill_percent}% = {tank.fill_volume:.0f} m³")
# Output: Fill: 50.0% = 500 m³

# Set by volume in cubic meters
tank.set_fill_level(300.0, unit='m³')
print(f"Fill: {tank.fill_percent}% = {tank.fill_volume:.0f} m³")
# Output: Fill: 30.0% = 300 m³

Tank Properties

Each tank provides various properties:

tank.set_fill_level(60, unit='percent')

# Basic properties
print(f"Total capacity: {tank.total_volume:.1f} m³")
print(f"Current fill: {tank.fill_volume:.1f} m³")
print(f"Fluid mass: {tank.fluid_mass:.0f} kg")

# Center of gravity (of the fluid)
cog = tank.center_of_gravity
print(f"CoG: X={cog[0]:.2f}, Y={cog[1]:.2f}, Z={cog[2]:.2f} m")

# Free surface moments (for stability)
print(f"I_transverse: {tank.free_surface_moment_t:.1f} m⁴")
print(f"I_longitudinal: {tank.free_surface_moment_l:.1f} m⁴")

Free Surface Effects

When a tank has a free surface (not completely full or empty), the liquid can shift when the vessel heels, causing a virtual rise in the center of gravity. This is known as the free surface effect.

Understanding the Correction

The free surface correction is calculated as:

GG' = (I × ρ_fluid) / (∇ × ρ_water)

Where: - GG’ = virtual rise of center of gravity (m) - I = second moment of area of the free surface (m⁴) - ρ_fluid = density of the tank fluid (kg/m³) - ∇ = displacement volume of the vessel (m³) - ρ_water = density of seawater (kg/m³)

The corrected GM is:

GM_corrected = GM_dry - GG'

Integration with Vessel

Adding Tanks to a Vessel

from pynavaltoolbox import Vessel, Hull
from pynavaltoolbox.tanks import Tank

# Create vessel with tanks
hull = Hull("ship.stl")

tanks = [
    Tank.from_box(
        x_min=20.0, x_max=40.0,
        y_min=-4.0, y_max=4.0,
        z_min=1.0, z_max=5.0,
        fluid_density=850.0,
        name="Fuel_P"
    ),
    Tank.from_box(
        x_min=50.0, x_max=80.0,
        y_min=-6.0, y_max=6.0,
        z_min=0.0, z_max=2.0,
        fluid_density=1025.0,
        name="Ballast_Aft"
    )
]

# Set fill levels
tanks[0].set_fill_level(60, unit='percent')
tanks[1].set_fill_level(80, unit='percent')

vessel = Vessel(hull, tanks=tanks)

# Get combined tank properties
print(f"Total tanks mass: {vessel.get_total_tanks_mass():.0f} kg")
print(f"Combined CoG: {vessel.get_tanks_center_of_gravity()}")

Hydrostatics with Free Surface Correction

When calculating hydrostatics with tanks, the calculator automatically includes free surface corrections:

from pynavaltoolbox.hydrostatics import HydrostaticsCalculator

calc = HydrostaticsCalculator(vessel)
state = calc.calculate_at_draft(draft=6.0, vcg=7.0)

print(f"GM dry: {state.gmt_dry:.3f} m")
print(f"Free surface correction: {state.free_surface_correction_t:.3f} m")
print(f"GM wet: {state.gmt_wet:.3f} m")

Stability with Free Surface Correction

The GZ curve also includes free surface corrections:

from pynavaltoolbox.stability import StabilityCalculator

stab = StabilityCalculator(vessel)
curve = stab.calculate_gz_curve(
    displacement_mass=5000000,  # kg
    cog=(45.0, 0.0, 7.0),       # LCG, TCG, VCG
    heels=list(range(0, 61, 5))
)

print(f"GG' (free surface effect): {curve.gg_prime:.3f} m")
print(f"\nHeel   GZ_dry   GZ_wet")
for pt in curve.points:
    print(f"{pt.heel:5.0f}° {pt.gz:7.3f}  {pt.gz_corrected:7.3f}")

# Compare stability parameters
print(f"\nMax GZ (dry): {curve.get_max_gz(corrected=False):.3f} m")
print(f"Max GZ (wet): {curve.get_max_gz(corrected=True):.3f} m")

Complete Example

Here’s a complete example showing a loading condition analysis:

from pynavaltoolbox import Hull, Vessel
from pynavaltoolbox.tanks import Tank
from pynavaltoolbox.hydrostatics import HydrostaticsCalculator
from pynavaltoolbox.stability import StabilityCalculator

# Load hull
hull = Hull("cargo_ship.stl")

# Create tanks
fuel_ps = Tank.from_box(
    x_min=30.0, x_max=50.0,
    y_min=-8.0, y_max=-2.0,
    z_min=1.0, z_max=6.0,
    fluid_density=850.0,
    name="Fuel_PS"
)
fuel_ps.set_fill_level(70, unit='percent')

fuel_sb = Tank.from_box(
    x_min=30.0, x_max=50.0,
    y_min=2.0, y_max=8.0,
    z_min=1.0, z_max=6.0,
    fluid_density=850.0,
    name="Fuel_SB"
)
fuel_sb.set_fill_level(70, unit='percent')

ballast = Tank.from_box_hull_intersection(
    hull,
    x_min=10.0, x_max=90.0,
    y_min=-12.0, y_max=12.0,
    z_min=0.0, z_max=1.5,
    fluid_density=1025.0,
    name="DoubleBottom"
)
ballast.set_fill_level(50, unit='percent')

# Create vessel
vessel = Vessel(hull, tanks=[fuel_ps, fuel_sb, ballast])

# Find equilibrium
hydro = HydrostaticsCalculator(vessel)
state = hydro.find_equilibrium(
    displacement_mass=8000000,  # Target displacement
    center_of_gravity=(50.0, 0.0, 8.0),
    initial_draft=6.0
)

print("=== Hydrostatic Analysis ===")
print(f"Draft: {state.draft:.2f} m")
print(f"Displacement: {state.displacement_mass:.0f} kg")
print(f"GM dry: {state.gmt_dry:.3f} m")
print(f"Free surface corr: {state.free_surface_correction_t:.3f} m")
print(f"GM wet: {state.gmt_wet:.3f} m")

# Calculate GZ curve
stab = StabilityCalculator(vessel)
gz_curve = stab.calculate_gz_curve(
    displacement_mass=state.displacement_mass,
    cog=(50.0, 0.0, 8.0),
    heels=list(range(0, 71, 5))
)

print("\n=== Stability Analysis ===")
print(f"GG' = {gz_curve.gg_prime:.3f} m")
print(f"Max GZ: {gz_curve.get_max_gz():.3f} m at {gz_curve.get_angle_of_max_gz():.0f}°")
print(f"Range of stability: {gz_curve.get_range_of_stability():.1f}°")

Visualizing Vessels with Tanks

The VesselVisualizer class provides methods to visualize vessels with their tanks, using transparent hulls to see tank contents.

Interactive 3D Viewer

from pynavaltoolbox.hydrostatics import VesselVisualizer

viz = VesselVisualizer(vessel)

# Open interactive 3D window
viz.show_with_tanks(
    hull_opacity=0.3,       # Transparent hull (0-1)
    show_fluid=True,        # Show current fluid levels
    show_tank_bounds=True   # Show tank outlines
)

Controls in the 3D viewer:

  • Left mouse: Rotate

  • Right mouse: Zoom

  • Middle mouse: Pan

  • ‘q’: Close window

Saving Screenshots

# Save a view with tanks
viz.save_view_with_tanks(
    "vessel_tanks.png",
    view_type='iso',        # 'iso', 'side', 'front', 'rear', 'top'
    hull_opacity=0.25,
    show_fluid=True,
    show_tank_bounds=True,
    draft=6.0               # Optional: show waterline
)

Visualization Features

  • Hull transparency: Adjust hull_opacity to see inside the vessel

  • Tank colors: Each tank is rendered in a different color

  • Fluid level: Solid rendering of current fill level

  • Tank bounds: Wireframe showing tank geometry

  • Waterline: Optional visualization at specified draft