Lab 9 - Turn on a hydro table and a tank¶
The box-hull model works anywhere — no data required — but it trades accuracy for convenience. Real ships carry hydrostatic tables surveyed by the yard, and real stability calculations include a free-surface correction for partially-filled tanks. In this lab you will switch both on, read the numbers that change, and understand why they change.
All commands run from the repository root. The binary must be built first (make).
What you need¶
| File | Role |
|---|---|
examples/sample_ship.cfg |
Minimal ship — no hydro table, no tanks |
examples/sample_ship_full.cfg |
Same ship plus hydrostatic_table= and tank_config= keys |
examples/sample_hydro.csv |
Nine-column CSV table: draft → displacement, KB, BM, TPC, MTC, … |
examples/sample_tanks.csv |
Five partially-filled tanks: ballast, double-bottoms, fuel, fresh water |
examples/sample_cargo.txt |
Five general cargo items, total 1 500 t |
Step 1 — Confirm the build¶
You should see a version line such as CargoForge 3.0.0. If make errors, check that you have
a C99-capable compiler (cc --version) and that you are in the repository root.
Step 2 — Run with the minimal ship (box-hull, no tanks)¶
Look for the Stability block in the output:
Hydrostatics: Box-hull approximation
...
Stability
KG / KB / BM : 2.20 / 0.64 / 48.62 m
GM (transverse) : 47.06 m | WARNING - Too stiff
Trim (by stern) : -2.458 m
Heel : -11.00 deg
Box-hull numbers
Draft is reported as 1.21 m because the code divides displaced volume by
\(L \times B \times C_B\) (block coefficient 0.75). KB is \(0.53 \times \text{draft}\).
BM uses a rectangular second moment of area with a waterplane coefficient of 0.85.
GM = 47 m is physically absurd for a real ship — that is the hallmark of the approximation.
There are no tanks to correct for, so free_surface_correction is 0.
Step 3 — Inspect the full config¶
Open examples/sample_ship_full.cfg in any text viewer:
# Hydrostatic table (enables table-based calculations)
hydrostatic_table=examples/sample_hydro.csv
# Tank configuration (enables free surface correction)
tank_config=examples/sample_tanks.csv
# Permissible longitudinal strength limits (from class society)
permissible_sf_tonnes=5000
permissible_bm_hog_t_m=120000
permissible_bm_sag_t_m=100000
Two new keys activate two new subsystems. Every other field is identical to sample_ship.cfg.
Now look at the hydrostatic table itself:
Expected:
# draft_m,displacement_t,KM_m,KB_m,BM_m,TPC_t_cm,MTC_t_m,waterplane_m2,LCB_m
2.0,2306,13.20,1.06,12.14,9.60,185.0,3375,1.5
3.0,3544,9.92,1.58,8.34,10.20,195.0,3570,1.2
4.0,4838,8.23,2.10,6.13,10.70,205.0,3720,0.9
5.0,6181,7.25,2.63,4.62,11.10,215.0,3840,0.6
Each row is one surveyed waterline. parse_hydro_table in hydrostatics.c reads this and stores
it in a HydroTable hanging off ship->hydro. When the ship is loaded,
hydro_draft_from_displacement inverse-interpolates draft from the computed displacement, then
hydro_interpolate reads back KB and BM at that draft.
And the tank file:
Expected:
# id,length_m,breadth_m,height_m,pos_x_m,pos_y_m,pos_z_m,fill_fraction,density_t_m3
BallastFP,8.0,12.0,6.0,140.0,0.0,0.0,0.50,1.025
BallastDB_P,20.0,4.0,6.0,75.0,-10.0,0.0,0.75,1.025
BallastDB_S,20.0,4.0,6.0,75.0,10.0,0.0,0.75,1.025
FuelOil_1,10.0,6.0,5.0,30.0,-8.0,0.0,0.60,0.950
FreshWater,5.0,5.0,4.0,20.0,0.0,0.0,0.85,1.000
None of the tanks is completely empty or completely full — which is exactly when free-surface effects exist. The forepeak ballast is 50 % full; both double-bottom tanks are 75 % full.
Step 4 — Validate both configs parse cleanly¶
Expected:
Validating ship configuration: examples/sample_ship_full.cfg
[OK] Ship configuration is valid
Validating cargo manifest: examples/sample_cargo.txt
[OK] Cargo manifest is valid
[OK] All validation checks passed!
validate calls parse_ship_config and parse_cargo_list but stops before analysis.
Use it whenever you edit a config to catch typos early.
Step 5 — Run with the full ship and observe the differences¶
Find the Stability block again:
Hydrostatics: Table interpolation
...
Stability
KG / KB / BM : 2.13 / 2.08 / 6.21 m
GM (transverse) : 6.16 m | WARNING - Too stiff
Free surface corr : -0.339 m
GM (corrected) : 5.82 m
Trim (by stern) : -4.902 m
Heel : -57.53 deg
And the Longitudinal Strength block, which only appears when permissible_* keys are set:
Longitudinal Strength
Max Shear Force : 1484 t
Max Bending Moment : 32376 t-m
Status : WITHIN LIMITS
Step 6 — Understand every number that changed¶
Work through the differences one at a time.
6.1 Draft: 1.21 m → 3.96 m¶
The displacement of this load is about 3 500 t (lightship 2 000 t + cargo 1 500 t + tank liquid). The box-hull formula divides displaced volume by \(L \times B \times C_B\):
The table knows this ship's actual hull form. At 3 500 t it interpolates between the 3.0 t and 4.0 t rows and returns a draft close to 3.96 m — the real waterline for this vessel.
6.2 KB: 0.64 m → 2.08 m¶
Box-hull uses the constant \(KB = 0.53 \times \text{draft}\). At draft 1.21 m that gives 0.64 m.
The table row near 4 m draft has \(KB = 2.10\) m. Interpolation for 3.96 m yields 2.08 m — far more accurate because it reflects the actual underwater hull shape.
6.3 BM: 48.62 m → 6.21 m¶
This is the most dramatic change. BM = \(I_T / \nabla\) where \(I_T\) is the second moment of the waterplane area and \(\nabla\) is displaced volume.
The box-hull calculates \(I_T = L \times B^3 / 12 \times 0.85\) at the box-hull draft of 1.21 m, giving a huge \(\nabla\) denominator that is too small (shallow draft → small volume). The table's BM at 3.96 m draft is 6.21 m.
| KB | BM | KG | GM | |
|---|---|---|---|---|
| Box-hull | 0.64 | 48.62 | 2.20 | 47.06 m |
| Table | 2.08 | 6.21 | 2.13 | 6.16 m |
A GM of 47 m would make the ship roll like a pendulum on a rigid pivot — comfortable for cargo but impossible in reality. The table-corrected 6.16 m is still stiff but physically plausible for a lightly loaded general-cargo vessel.
6.4 Free-surface correction: 0 → −0.339 m¶
When liquid in a partially filled tank shifts as the ship heels, the effect is identical to
raising the centre of gravity. calculate_free_surface_moment in tanks.c computes, for each
tank:
where \(\rho\) is liquid density, \(l\) is tank length, \(b\) is breadth. Empty and full tanks contribute zero (no free surface). The total FSM is divided by displacement to get a virtual KG rise in metres:
With five partially-filled tanks the correction is −0.339 m, meaning the effective GM
drops from 6.16 m to 5.82 m. The sign convention in the output shows Free surface corr:
-0.339 m (a reduction in stability). All subsequent criteria — GZ curve, heel angle — use
GM_corrected.
Why the correction always reduces stability
A free surface always makes the ship less stable. The liquid moving to the low side adds a heeling moment that partially opposes the righting lever. You cannot design your way out of it — you can only minimize it by using longitudinally divided tanks (smaller \(b\)) or by running tanks fully empty or fully pressed-up.
6.5 Longitudinal strength — a new output section¶
The minimal config has no permissible_* keys, so ship->strength_limits is NULL and the
check is skipped. The full config sets all three limits. calculate_longitudinal_strength
distributes weight and buoyancy across 20 hull stations, integrates to shear force, integrates
again to bending moment, and compares peak values to the class-society limits.
Step 7 — Extract numbers as JSON for comparison¶
The --format json flag lets you diff runs programmatically.
./cargoforge optimize examples/sample_ship.cfg examples/sample_cargo.txt --format json \
2>/dev/null | python3 -c "
import json,sys; r=json.load(sys.stdin)['analysis']
print('BOX draft={draft:.3f} KB={kb:.3f} BM={bm:.3f} GM={gm:.3f} FSC={free_surface_correction:.3f} GMc={gm_corrected:.3f}'.format(**r))
"
./cargoforge optimize examples/sample_ship_full.cfg examples/sample_cargo.txt --format json \
2>/dev/null | python3 -c "
import json,sys; r=json.load(sys.stdin)['analysis']
print('TABLE draft={draft:.3f} KB={kb:.3f} BM={bm:.3f} GM={gm:.3f} FSC={free_surface_correction:.3f} GMc={gm_corrected:.3f}'.format(**r))
"
Expected output (values match to the third decimal):
BOX draft=1.214 KB=0.643 BM=48.619 GM=47.061 FSC=0.000 GMc=47.061
TABLE draft=3.962 KB=2.080 BM=6.213 GM=6.162 FSC=0.339 GMc=5.823
The field hydro_table_used in the JSON is false for the first run and true for the second —
a reliable programmatic flag for which code path was taken.
Step 8 — Run the unit tests¶
The test suite covers the hydrostatics interpolation and tank free-surface functions directly:
The targets test_hydrostatics and test_tanks exercise parse_hydro_table,
hydro_interpolate, calculate_free_surface_moment, and calculate_total_fsm. A passing run
prints no output and exits 0 for each binary.
For memory-safety confidence:
This rebuilds with AddressSanitizer and UndefinedBehaviorSanitizer before running the same suite. Any heap overflow, use-after-free, or signed integer overflow will abort with a diagnostic.
Solution¶
If your run does not match the expected numbers, work through this checklist:
-
Wrong config file. Double-check you used
sample_ship_full.cfg(notsample_ship.cfg) for the table run. The lineHydrostatics: Table interpolationconfirms it. -
CSV paths are relative to the working directory.
sample_ship_full.cfgcontainshydrostatic_table=examples/sample_hydro.csv. If you run./cargoforgefrom a subdirectory, that path does not resolve. Always run from the repository root. -
Build is stale. If you edited source files, re-run
makebefore./cargoforge. -
JSON parse fails. The
--format jsonoutput goes to stdout; progress messages go to stderr. The2>/dev/nullin Step 7 suppresses the progress lines sopython3only sees JSON. Remove it if you want to see the progress messages alongside.
The corrected JSON fields for the full-config run are:
"draft": 3.962,
"kb": 2.080,
"bm": 6.213,
"gm": 6.162,
"free_surface_correction": 0.339,
"gm_corrected": 5.823,
"hydro_table_used": true
Recap¶
- Adding
hydrostatic_table=to the ship config switchesperform_analysisfrom the box-hull constant-coefficient path tohydro_draft_from_displacement+hydro_interpolate, yielding accurate draft, KB, and BM from the surveyed table. - Adding
tank_config=activatescalculate_virtual_kg_rise, which subtracts the free-surface correction from GM; tanks at 0 % or 100 % fill contribute nothing; partial fill contributes \(\rho \, l \, b^3 / 12 \div \Delta\). - The wall-sided GZ curve and all six IMO criteria always use
gm_corrected, never the raw GM. - Longitudinal strength checks (
SWSF,SWBM) only appear when the threepermissible_*keys are present in the ship config. make test-asanis the fastest way to verify that a config change has not introduced a memory error in the parser or analysis path.
Next: Sanitizers (ASan/UBSan).