diff --git a/README.md b/README.md index d8cf66e..44e7c5d 100644 --- a/README.md +++ b/README.md @@ -57,13 +57,13 @@ vessel = Vessel(hull) calc = HydrostaticsCalculator(vessel, water_density=1025.0) # Option 1: At draft with VCG -state = calc.calculate_at_draft(5.0, vcg=6.0) +state = calc.from_draft(5.0, vcg=6.0) print(f"Volume: {state.volume:.1f} m³") print(f"Waterplane Area: {state.waterplane_area:.1f} m²") print(f"GMT (wet): {state.gmt:.3f} m") # Option 2: Find draft for displacement -state_disp = calc.calculate_at_displacement(512500.0) +state_disp = calc.from_displacement(512500.0) print(f"Draft: {state_disp.draft:.3f} m") # Calculate GZ curve @@ -101,7 +101,7 @@ let vessel = Vessel::new(hull); // Calculate hydrostatics let calc = HydrostaticsCalculator::new(&vessel, 1025.0); -let state = calc.calculate_at_draft(5.0, 0.0, 0.0, 0.0)?; +let state = calc.from_draft(5.0, 0.0, 0.0, None)?; println!("Volume: {} m³", state.volume); // Calculate GZ curve diff --git a/docs/source/api/python/hydrostatics.rst b/docs/source/api/python/hydrostatics.rst index 611b254..547d1af 100644 --- a/docs/source/api/python/hydrostatics.rst +++ b/docs/source/api/python/hydrostatics.rst @@ -13,7 +13,7 @@ HydrostaticsCalculator :param vessel: The vessel to calculate hydrostatics for :param water_density: Water density in kg/m³ (default: 1025.0 for seawater) - .. py:method:: calculate_at_draft(draft, trim=0.0, heel=0.0, vcg=None) + .. py:method:: from_draft(draft, trim=0.0, heel=0.0, vcg=None) Calculates hydrostatics at a given draft, trim, and heel. @@ -24,7 +24,7 @@ HydrostaticsCalculator :type vcg: float or None :returns: HydrostaticState with all properties - .. py:method:: calculate_at_displacement(displacement_mass, vcg=None, cog=None, trim=None, heel=None) + .. py:method:: from_displacement(displacement_mass, vcg=None, cog=None, trim=None, heel=None) Calculates hydrostatics for a given displacement, finding the required draft. diff --git a/docs/source/tutorials/hydrostatics.rst b/docs/source/tutorials/hydrostatics.rst index 4d01777..38829de 100644 --- a/docs/source/tutorials/hydrostatics.rst +++ b/docs/source/tutorials/hydrostatics.rst @@ -35,7 +35,7 @@ Calculate hydrostatics at a specific draft: calc = HydrostaticsCalculator(vessel, water_density=1025.0) # Calculate at draft 6.0m, even keel - state = calc.calculate_at_draft( + state = calc.from_draft( draft=6.0, trim=0.0, heel=0.0, @@ -80,17 +80,17 @@ Find the draft for a known displacement: target_displacement = 8635000 # kg # Find draft for displacement (level keel) - state = calc.calculate_at_displacement(target_displacement) + state = calc.from_displacement(target_displacement) print(f"Equilibrium draft: {state.draft:.3f}m") print(f"Actual displacement: {state.displacement:.0f} kg") # With VCG for GM calculation - state = calc.calculate_at_displacement(target_displacement, vcg=7.555) + state = calc.from_displacement(target_displacement, vcg=7.555) print(f"Draft: {state.draft:.3f}m") print(f"GMT: {state.gmt:.3f}m") # Or with full COG (LCG, TCG, VCG) for advanced use - state = calc.calculate_at_displacement( + state = calc.from_displacement( target_displacement, cog=(71.67, 0.0, 7.555) ) @@ -104,7 +104,7 @@ Hydrostatics change with heel angle: # Compare upright vs heeled for heel in [0, 10, 20, 30]: - state = calc.calculate_at_draft(6.15, 0.0, heel, 7.555) + state = calc.from_draft(6.15, 0.0, heel, 7.555) print(f"Heel {heel:2d}°: Vol={state.volume:.1f}m³, VCB={state.vcb:.2f}m") Fresh vs Salt Water @@ -122,8 +122,8 @@ Density affects displacement: draft = 6.0 - state_fresh = calc_fresh.calculate_at_draft(draft, 0.0, 0.0, 7.5) - state_salt = calc_salt.calculate_at_draft(draft, 0.0, 0.0, 7.5) + state_fresh = calc_fresh.from_draft(draft, 0.0, 0.0, 7.5) + state_salt = calc_salt.from_draft(draft, 0.0, 0.0, 7.5) print(f"Fresh water: {state_fresh.displacement:.0f} kg") print(f"Salt water: {state_salt.displacement:.0f} kg") diff --git a/docs/source/userguide/quickstart.rst b/docs/source/userguide/quickstart.rst index 2763323..270be18 100644 --- a/docs/source/userguide/quickstart.rst +++ b/docs/source/userguide/quickstart.rst @@ -54,7 +54,7 @@ Calculate hydrostatic properties at a given draft: calc = HydrostaticsCalculator(vessel, water_density=1025.0) # Calculate at specific draft - state = calc.calculate_at_draft( + state = calc.from_draft( draft=6.0, # meters trim=0.0, # degrees heel=0.0, # degrees diff --git a/python/README.md b/python/README.md index 3f231ce..8e2a0bc 100644 --- a/python/README.md +++ b/python/README.md @@ -63,7 +63,7 @@ vessel = Vessel(hull) calc = HydrostaticsCalculator(vessel, water_density=1025.0) # Option 1: At draft with VCG (computes stability) -state = calc.calculate_at_draft(draft=5.0, vcg=7.0) +state = calc.from_draft(draft=5.0, vcg=7.0) print(f"Volume: {state.volume:.1f} m³") print(f"Displacement: {state.displacement:.0f} kg") @@ -74,7 +74,7 @@ print(f"GMT (wet): {state.gmt:.3f} m") print(f"GMT (dry): {state.gmt_dry:.3f} m") # Option 2: Find draft for displacement -state_disp = calc.calculate_at_displacement(512500.0) +state_disp = calc.from_displacement(512500.0) print(f"Draft for {state_disp.displacement:.0f}kg: {state_disp.draft:.3f} m") ``` @@ -156,7 +156,7 @@ catamaran.add_hull(starboard_hull, offset=(0.0, 8.0, 0.0)) # 8m beam between hu # Calculate hydrostatics calc = HydrostaticsCalculator(catamaran, water_density=1025.0) -state = calc.calculate_at_draft(1.5) +state = calc.from_draft(1.5) print(f"Catamaran displacement: {state.displacement:.0f} kg") print(f"Waterplane area: {state.waterplane_area:.1f} m²") diff --git a/python/navaltoolbox/__init__.py b/python/navaltoolbox/__init__.py index faacbe4..c6e16e4 100644 --- a/python/navaltoolbox/__init__.py +++ b/python/navaltoolbox/__init__.py @@ -36,7 +36,7 @@ Example: >>> hull = Hull("ship.stl") >>> vessel = Vessel(hull) >>> calc = HydrostaticsCalculator(vessel) - >>> state = calc.calculate_at_draft(5.0) + >>> state = calc.from_draft(5.0) >>> print(f"Displacement: {state.displacement:.0f} kg") """ diff --git a/python/navaltoolbox/navaltoolbox.pyi b/python/navaltoolbox/navaltoolbox.pyi index f2932ff..bf7db62 100644 --- a/python/navaltoolbox/navaltoolbox.pyi +++ b/python/navaltoolbox/navaltoolbox.pyi @@ -157,11 +157,21 @@ class Vessel: """Returns the Aft Perpendicular position (X coordinate).""" ... + @ap.setter + def ap(self, value: float) -> None: + """Sets the Aft Perpendicular position (X coordinate).""" + ... + @property def fp(self) -> float: """Returns the Forward Perpendicular position (X coordinate).""" ... + @fp.setter + def fp(self, value: float) -> None: + """Sets the Forward Perpendicular position (X coordinate).""" + ... + @property def lbp(self) -> float: """Returns the Length Between Perpendiculars in meters.""" @@ -486,9 +496,33 @@ class HydrostaticState: heel: Heel angle in degrees. volume: Submerged volume in m³. displacement: Displacement mass in kg. + cob: Center of buoyancy as tuple (lcb, tcb, vcb). + cog: Center of gravity as tuple (lcg, tcg, vcg) if specified, None otherwise. lcb: Longitudinal center of buoyancy (X) in meters. tcb: Transverse center of buoyancy (Y) in meters. vcb: Vertical center of buoyancy (Z) in meters. + lcg: Longitudinal center of gravity (X) in meters, or None. + tcg: Transverse center of gravity (Y) in meters, or None. + vcg: Vertical center of gravity (Z) in meters, or None. + waterplane_area: Waterplane area in m². + lcf: Longitudinal center of floatation (X) in meters. + bmt: Transverse metacentric radius in meters. + bml: Longitudinal metacentric radius in meters. + gmt: Transverse metacentric height with FSC in meters, or None. + gml: Longitudinal metacentric height with FSC in meters, or None. + gmt_dry: Transverse metacentric height without FSC in meters, or None. + gml_dry: Longitudinal metacentric height without FSC in meters, or None. + lwl: Waterline length in meters. + bwl: Waterline breadth in meters. + los: Length overall submerged in meters. + wetted_surface_area: Wetted surface area in m². + midship_area: Midship section area in m². + cm: Midship coefficient. + cb: Block coefficient. + cp: Prismatic coefficient. + free_surface_correction_t: Transverse FSC in meters. + free_surface_correction_l: Longitudinal FSC in meters. + stiffness_matrix: 6x6 hydrostatic stiffness matrix (flattened). """ draft: float @@ -496,9 +530,83 @@ class HydrostaticState: heel: float volume: float displacement: float - lcb: float - tcb: float - vcb: float + + @property + def cob(self) -> Tuple[float, float, float]: + """Center of buoyancy (lcb, tcb, vcb) in meters.""" + ... + + @property + def cog(self) -> Tuple[float, float, float] | None: + """Center of gravity (lcg, tcg, vcg) if specified, None otherwise.""" + ... + + @property + def lcb(self) -> float: + """Longitudinal center of buoyancy (X) in meters.""" + ... + + @property + def tcb(self) -> float: + """Transverse center of buoyancy (Y) in meters.""" + ... + + @property + def vcb(self) -> float: + """Vertical center of buoyancy (Z) in meters.""" + ... + + @property + def lcg(self) -> float | None: + """Longitudinal center of gravity (X) in meters, or None.""" + ... + + @property + def tcg(self) -> float | None: + """Transverse center of gravity (Y) in meters, or None.""" + ... + + @property + def vcg(self) -> float | None: + """Vertical center of gravity (Z) in meters, or None.""" + ... + + waterplane_area: float + lcf: float + bmt: float + bml: float + + @property + def gmt(self) -> float | None: + """Transverse metacentric height with FSC in meters, or None if VCG not specified.""" + ... + + @property + def gml(self) -> float | None: + """Longitudinal metacentric height with FSC in meters, or None if VCG not specified.""" + ... + + @property + def gmt_dry(self) -> float | None: + """Transverse metacentric height without FSC in meters, or None.""" + ... + + @property + def gml_dry(self) -> float | None: + """Longitudinal metacentric height without FSC in meters, or None.""" + ... + + lwl: float + bwl: float + los: float + wetted_surface_area: float + midship_area: float + cm: float + cb: float + cp: float + free_surface_correction_t: float + free_surface_correction_l: float + stiffness_matrix: List[float] class HydrostaticsCalculator: @@ -511,7 +619,7 @@ class HydrostaticsCalculator: >>> hull = Hull("hull.stl") >>> vessel = Vessel(hull) >>> calc = HydrostaticsCalculator(vessel, water_density=1025.0) - >>> state = calc.calculate_at_draft(draft=5.0) + >>> state = calc.from_draft(draft=5.0) >>> print(f"Displacement: {state.displacement:.0f} kg") """ @@ -524,12 +632,12 @@ class HydrostaticsCalculator: """ ... - def calculate_at_draft( + def from_draft( self, draft: float, trim: float = 0.0, heel: float = 0.0, - vcg: float = 0.0, + vcg: float | None = None, ) -> HydrostaticState: """Calculate hydrostatics at a given draft, trim, and heel. @@ -537,7 +645,7 @@ class HydrostaticsCalculator: draft: Draft at midship in meters. trim: Trim angle in degrees (default: 0). heel: Heel angle in degrees (default: 0). - vcg: Vertical center of gravity in meters (default: 0). + vcg: Optional vertical center of gravity in meters for GMT/GML calculation. Returns: HydrostaticState with calculated properties. @@ -547,17 +655,39 @@ class HydrostaticsCalculator: """ ... - def find_draft_for_displacement(self, displacement_mass: float) -> float: - """Find the draft for a given displacement mass. + def from_displacement( + self, + displacement_mass: float, + vcg: float | None = None, + cog: Tuple[float, float, float] | None = None, + trim: float | None = None, + heel: float | None = None, + ) -> HydrostaticState: + """Calculate hydrostatics for a given displacement with optional constraints. Args: displacement_mass: Target displacement in kg. + vcg: Optional vertical center of gravity (m) for GM calculations. + cog: Optional (lcg, tcg, vcg) tuple in meters for full COG specification. + (overrides vcg if both are provided) + trim: Optional trim angle in degrees. + heel: Optional heel angle in degrees. Returns: - Draft in meters that achieves the target displacement. + Complete HydrostaticState. Raises: - ValueError: If the displacement cannot be achieved. + ValueError: If constraints are invalid or unsatisfiable. + + Examples: + >>> # Basic: find draft for displacement + >>> state = calc.from_displacement(8635000.0) + + >>> # With VCG only: compute GMT/GML + >>> state = calc.from_displacement(8635000.0, vcg=7.555) + + >>> # With full COG: for trim optimization + >>> state = calc.from_displacement(8635000.0, cog=(71.67, 0.0, 7.555)) """ ... diff --git a/python/tests/test_bindings.py b/python/tests/test_bindings.py index 0b3e0f6..16fa5b9 100644 --- a/python/tests/test_bindings.py +++ b/python/tests/test_bindings.py @@ -307,7 +307,7 @@ class TestDTMB5415: from navaltoolbox import HydrostaticsCalculator calc = HydrostaticsCalculator(dtmb5415_vessel, 1025.0) - state = calc.calculate_at_draft(6.15, 0.0, 0.0, self.VCG) + state = calc.from_draft(6.15, 0.0, 0.0, self.VCG) print(f"DTMB5415 at T=6.15m: Volume={state.volume:.1f}m³") @@ -484,7 +484,7 @@ class TestWaterplaneCalculations: calc = HydrostaticsCalculator(vessel, 1025.0) # Calculate at reference draft with VCG - state = calc.calculate_at_draft(6.15, vcg=7.555) + state = calc.from_draft(6.15, vcg=7.555) # Calculate KMt kmt = state.vcb + state.bmt @@ -533,7 +533,7 @@ class TestWaterplaneCalculations: vessel = Vessel(hull) calc = HydrostaticsCalculator(vessel, 1025.0) - state = calc.calculate_at_draft(6.15, vcg=7.555) + state = calc.from_draft(6.15, vcg=7.555) # Check that all waterplane properties exist and are reasonable assert hasattr(state, "waterplane_area") @@ -570,7 +570,7 @@ class TestWaterplaneCalculations: calc = HydrostaticsCalculator(vessel, 1025.0) # Calculate without VCG - state = calc.calculate_at_draft(6.15) + state = calc.from_draft(6.15) # GMT/GML should be None assert state.gmt is None, "GMT should be None when VCG not provided" @@ -640,7 +640,7 @@ class TestFreeSurfaceCorrection: # 3. Calculate calc = HydrostaticsCalculator(vessel, 1025.0) - state = calc.calculate_at_draft(5.0, vcg=5.0) + state = calc.from_draft(5.0, vcg=5.0) # 4. Validate gmt_wet = state.gmt diff --git a/rust/src/hydrostatics/calculator.rs b/rust/src/hydrostatics/calculator.rs index 416c096..6c24bf5 100644 --- a/rust/src/hydrostatics/calculator.rs +++ b/rust/src/hydrostatics/calculator.rs @@ -51,7 +51,7 @@ impl<'a> HydrostaticsCalculator<'a> { /// * `trim` - Trim angle in degrees (positive = bow down) /// * `heel` - Heel angle in degrees (positive = starboard down) /// * `vcg` - Optional vertical center of gravity for GM calculation - pub fn calculate_at_draft( + pub fn from_draft( &self, draft: f64, trim: f64, @@ -326,7 +326,7 @@ impl<'a> HydrostaticsCalculator<'a> { /// * `cog` - Optional full COG [LCG, TCG, VCG] (overrides vcg if provided) /// * `trim` - Optional trim angle in degrees (default 0.0) /// * `heel` - Optional heel angle in degrees (default 0.0) - pub fn calculate_at_displacement( + pub fn from_displacement( &self, displacement_mass: f64, vcg: Option, @@ -376,7 +376,7 @@ impl<'a> HydrostaticsCalculator<'a> { for _ in 0..max_iter { let mid = (low + high) / 2.0; - if let Some(state) = self.calculate_at_draft(mid, fixed_trim, fixed_heel, effective_vcg) + if let Some(state) = self.from_draft(mid, fixed_trim, fixed_heel, effective_vcg) { let diff = state.volume - target_volume; @@ -405,7 +405,7 @@ impl<'a> HydrostaticsCalculator<'a> { // Convergence not achieved within max iterations // Return best estimate let final_draft = (low + high) / 2.0; - self.calculate_at_draft(final_draft, fixed_trim, fixed_heel, effective_vcg) + self.from_draft(final_draft, fixed_trim, fixed_heel, effective_vcg) .map(|state| HydrostaticState { cog, // Only set full COG if it was provided ..state @@ -673,7 +673,7 @@ mod tests { let calc = HydrostaticsCalculator::new(&vessel, 1025.0); // At draft 5m, volume should be 10 * 10 * 5 = 500 m³ - let state = calc.calculate_at_draft(5.0, 0.0, 0.0, None).unwrap(); + let state = calc.from_draft(5.0, 0.0, 0.0, None).unwrap(); assert!( (state.volume - 500.0).abs() < 1.0, "Volume was {}", @@ -682,7 +682,7 @@ mod tests { } #[test] - fn test_calculate_at_displacement_level() { + fn test_from_displacement_level() { let hull = create_box_hull(10.0, 10.0, 10.0); let vessel = Vessel::new(hull); let calc = HydrostaticsCalculator::new(&vessel, 1025.0); @@ -692,7 +692,7 @@ mod tests { // Calculate at displacement with no other constraints (level keel) let state = calc - .calculate_at_displacement(target_disp, None, None, None, None) + .from_displacement(target_disp, None, None, None, None) .expect("Calculation failed"); assert!( @@ -740,7 +740,7 @@ mod tests { } #[test] - fn test_calculate_at_displacement_with_vcg() { + fn test_from_displacement_with_vcg() { let hull = create_box_hull(10.0, 10.0, 10.0); let vessel = Vessel::new(hull); let calc = HydrostaticsCalculator::new(&vessel, 1025.0); @@ -749,7 +749,7 @@ mod tests { // With VCG provided, should compute GMT/GML // Note: LCB/TCB assumed 0.0 for box hull, so just set VCG=7.0 let state = calc - .calculate_at_displacement(target_disp, Some(7.0), None, None, None) + .from_displacement(target_disp, Some(7.0), None, None, None) .expect("Calculation failed"); assert!((state.draft - 5.0).abs() < 0.01); @@ -775,12 +775,12 @@ mod tests { // Invalid: Trim provided but also LCG constrained (non-zero) let res = - calc.calculate_at_displacement(100000.0, None, Some([5.0, 0.0, 0.0]), Some(0.0), None); + calc.from_displacement(100000.0, None, Some([5.0, 0.0, 0.0]), Some(0.0), None); assert!(res.is_err(), "Should fail for both LCG and Trim specified"); // Invalid: Heel provided but also TCG constrained let res = - calc.calculate_at_displacement(100000.0, None, Some([0.0, 5.0, 0.0]), None, Some(0.0)); + calc.from_displacement(100000.0, None, Some([0.0, 5.0, 0.0]), None, Some(0.0)); assert!(res.is_err(), "Should fail for both TCG and Heel specified"); } } diff --git a/rust/src/hydrostatics/waterplane.rs b/rust/src/hydrostatics/waterplane.rs index 4abcd57..7d9235d 100644 --- a/rust/src/hydrostatics/waterplane.rs +++ b/rust/src/hydrostatics/waterplane.rs @@ -387,7 +387,7 @@ mod tests { let draft = 5.0; let vcg = 7.0; - let state = calc.calculate_at_draft(draft, 0.0, 0.0, Some(vcg)).unwrap(); + let state = calc.from_draft(draft, 0.0, 0.0, Some(vcg)).unwrap(); // Theoretical BM_t = B² / (12×T) = 100 / 60 = 1.667 m let expected_bmt = 10.0 * 10.0 / (12.0 * 5.0); @@ -441,7 +441,7 @@ mod tests { let vessel = Vessel::new_multi(vec![hull1, hull2]).unwrap(); let calc = HydrostaticsCalculator::new(&vessel, 1025.0); - let state = calc.calculate_at_draft(5.0, 0.0, 0.0, None).unwrap(); + let state = calc.from_draft(5.0, 0.0, 0.0, None).unwrap(); // Expected total area = 100 m² assert!( @@ -474,7 +474,7 @@ mod tests { let vessel = Vessel::new_multi(vec![hull1, hull2]).unwrap(); let calc = HydrostaticsCalculator::new(&vessel, 1025.0); - let state = calc.calculate_at_draft(5.0, 0.0, 0.0, None).unwrap(); + let state = calc.from_draft(5.0, 0.0, 0.0, None).unwrap(); // Expected area = 10 × 10 = 100 m² assert!( diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 315bdcd..305f9a4 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -33,7 +33,7 @@ //! let hull = Hull::from_stl("ship.stl")?; //! let vessel = Vessel::new(hull); //! let calc = HydrostaticsCalculator::new(&vessel, 1025.0); -//! let state = calc.calculate_at_draft(5.0, 0.0, 0.0)?; +//! let state = calc.from_draft(5.0, 0.0, 0.0)?; //! println!("Volume: {} m³", state.volume); //! ``` //! diff --git a/rust/src/mesh/loader.rs b/rust/src/mesh/loader.rs index 87c036f..e74a441 100644 --- a/rust/src/mesh/loader.rs +++ b/rust/src/mesh/loader.rs @@ -41,6 +41,16 @@ pub fn load_stl(path: &Path) -> Result { return Err(Error::new(ErrorKind::InvalidData, "STL file is empty")); } + // Handle leading whitespace in ASCII STL files. + // Some CAD software exports ASCII STL with leading spaces/newlines before "solid". + // We trim leading whitespace to ensure correct parsing. + let trimmed_start = buffer.iter().position(|&c| !c.is_ascii_whitespace()).unwrap_or(0); + let buffer = if trimmed_start > 0 { + buffer[trimmed_start..].to_vec() + } else { + buffer + }; + // Create a cursor over the buffer for parsing let mut cursor = Cursor::new(buffer); diff --git a/rust/src/python.rs b/rust/src/python.rs index c96079f..4d84469 100644 --- a/rust/src/python.rs +++ b/rust/src/python.rs @@ -159,12 +159,24 @@ impl PyVessel { self.inner.ap() } + /// Sets the Aft Perpendicular position. + #[setter] + fn set_ap(&mut self, ap: f64) { + self.inner.set_ap(ap); + } + /// Returns the Forward Perpendicular position. #[getter] fn fp(&self) -> f64 { self.inner.fp() } + /// Sets the Forward Perpendicular position. + #[setter] + fn set_fp(&mut self, fp: f64) { + self.inner.set_fp(fp); + } + /// Returns the Length Between Perpendiculars. #[getter] fn lbp(&self) -> f64 { @@ -715,8 +727,8 @@ impl PyHydrostaticsCalculator { /// /// Returns: /// HydrostaticState with all properties - #[pyo3(signature = (draft, trim=0.0, heel=0.0, vcg=None))] - fn calculate_at_draft( + #[pyo3(signature = (draft, trim=0.0, heel=0.0, vcg=None), name = "from_draft")] + fn from_draft( &self, draft: f64, trim: f64, @@ -724,7 +736,7 @@ impl PyHydrostaticsCalculator { vcg: Option, ) -> PyResult { let calc = RustHydroCalc::new(&self.vessel, self.water_density); - calc.calculate_at_draft(draft, trim, heel, vcg) + calc.from_draft(draft, trim, heel, vcg) .map(|s| s.into()) .ok_or_else(|| PyValueError::new_err("No submerged volume at this draft")) } @@ -747,18 +759,18 @@ impl PyHydrostaticsCalculator { /// /// Examples: /// >>> # Basic: find draft for displacement - /// >>> state = calc.calculate_at_displacement(8635000.0) + /// >>> state = calc.from_displacement(8635000.0) /// /// >>> # With VCG only: compute GMT/GML - /// >>> state = calc.calculate_at_displacement(8635000.0, vcg=7.555) + /// >>> state = calc.from_displacement(8635000.0, vcg=7.555) /// /// >>> # With full COG: for trim optimization - /// >>> state = calc.calculate_at_displacement(8635000.0, cog=(71.67, 0.0, 7.555)) + /// >>> state = calc.from_displacement(8635000.0, cog=(71.67, 0.0, 7.555)) /// /// >>> # With trim constraint - /// >>> state = calc.calculate_at_displacement(8635000.0, vcg=7.5, trim=2.0) - #[pyo3(signature = (displacement_mass, vcg=None, cog=None, trim=None, heel=None))] - fn calculate_at_displacement( + /// >>> state = calc.from_displacement(8635000.0, vcg=7.5, trim=2.0) + #[pyo3(signature = (displacement_mass, vcg=None, cog=None, trim=None, heel=None), name = "from_displacement")] + fn from_displacement( &self, displacement_mass: f64, vcg: Option, @@ -771,7 +783,7 @@ impl PyHydrostaticsCalculator { // Convert cog tuple to array if provided let cog_array = cog.map(|(lcg, tcg, vcg_val)| [lcg, tcg, vcg_val]); - calc.calculate_at_displacement(displacement_mass, vcg, cog_array, trim, heel) + calc.from_displacement(displacement_mass, vcg, cog_array, trim, heel) .map(|s| s.into()) .map_err(|e| PyValueError::new_err(e)) } diff --git a/rust/src/stability/calculator.rs b/rust/src/stability/calculator.rs index 2dd4cbe..c566307 100644 --- a/rust/src/stability/calculator.rs +++ b/rust/src/stability/calculator.rs @@ -371,7 +371,7 @@ impl<'a> StabilityCalculator<'a> { // Calculate hydrostatics at equilibrium let hydro_calc = HydrostaticsCalculator::new(self.vessel, self.water_density); let hydrostatics = hydro_calc - .calculate_at_displacement(displacement_mass, None, Some(cog), None, None) + .from_displacement(displacement_mass, None, Some(cog), None, None) .unwrap_or_default(); // Calculate GZ curve diff --git a/rust/tests/dtmb_validation.rs b/rust/tests/dtmb_validation.rs index f2b2666..12f29cf 100644 --- a/rust/tests/dtmb_validation.rs +++ b/rust/tests/dtmb_validation.rs @@ -17,7 +17,7 @@ fn test_dtmb5415_simman_validation() { let vcg = 7.555; let state = calc - .calculate_at_draft(draft, 0.0, 0.0, Some(vcg)) + .from_draft(draft, 0.0, 0.0, Some(vcg)) .expect("Failed to calculate hydrostatics"); println!("Hydrostatic State at T={}: {:#?}", draft, state); diff --git a/rust/tests/extended_hydrostatics.rs b/rust/tests/extended_hydrostatics.rs index 7d5a613..7906d69 100644 --- a/rust/tests/extended_hydrostatics.rs +++ b/rust/tests/extended_hydrostatics.rs @@ -15,11 +15,11 @@ fn test_box_hydrostatics() { // Since VCG is optional, we pass None here for basic geo checks, // but the python test passed None explicitly or implicitly. // Python test: - // state = self.calc.calculate_at_draft(draft, 0.0, 0.0) + // state = self.calc.from_draft(draft, 0.0, 0.0) // Implicit VCG=None. let state = calc - .calculate_at_draft(draft, 0.0, 0.0, None) + .from_draft(draft, 0.0, 0.0, None) .expect("Failed to calculate box hydrostatics"); println!("Box State at T=5.0: {:#?}", state); @@ -105,7 +105,7 @@ fn test_box_displacement_calculation() { // Calculate at displacement let state = calc - .calculate_at_displacement(target_disp, None, None, None, None) + .from_displacement(target_disp, None, None, None, None) .expect("Failed to calculate at displacement"); // Check draft diff --git a/rust/tests/integration_tests.rs b/rust/tests/integration_tests.rs index 5a36854..6618b09 100644 --- a/rust/tests/integration_tests.rs +++ b/rust/tests/integration_tests.rs @@ -143,7 +143,7 @@ mod box_barge_tests { let calc = HydrostaticsCalculator::new(&vessel, 1025.0); // At draft 5m, volume should be 10*10*5 = 500 m³ - let state = calc.calculate_at_draft(5.0, 0.0, 0.0, None).unwrap(); + let state = calc.from_draft(5.0, 0.0, 0.0, None).unwrap(); let expected_volume = 500.0; let error = (state.volume - expected_volume).abs(); @@ -188,13 +188,13 @@ mod hydrostatics_tests { } #[test] - fn test_calculate_at_draft_half_submerged() { + fn test_from_draft_half_submerged() { let vessel = create_test_hull(); let calc = HydrostaticsCalculator::new(&vessel, 1000.0); // At draft 1.0, half is submerged // Expected volume: 10 * 2 * 1 = 20 m³ - let state = calc.calculate_at_draft(1.0, 0.0, 0.0, None).unwrap(); + let state = calc.from_draft(1.0, 0.0, 0.0, None).unwrap(); let expected_volume = 20.0; let error = (state.volume - expected_volume).abs(); @@ -217,8 +217,8 @@ mod hydrostatics_tests { let calc_fresh = HydrostaticsCalculator::new(&vessel, 1000.0); let calc_salt = HydrostaticsCalculator::new(&vessel, 1025.0); - let state_fresh = calc_fresh.calculate_at_draft(1.0, 0.0, 0.0, None).unwrap(); - let state_salt = calc_salt.calculate_at_draft(1.0, 0.0, 0.0, None).unwrap(); + let state_fresh = calc_fresh.from_draft(1.0, 0.0, 0.0, None).unwrap(); + let state_salt = calc_salt.from_draft(1.0, 0.0, 0.0, None).unwrap(); // Same volume assert!( @@ -242,7 +242,7 @@ mod hydrostatics_tests { let vessel = create_test_hull(); let calc = HydrostaticsCalculator::new(&vessel, 1000.0); - let state = calc.calculate_at_draft(1.0, 0.0, 0.0, None).unwrap(); + let state = calc.from_draft(1.0, 0.0, 0.0, None).unwrap(); // Displacement = Volume * Density let expected_displacement = state.volume * 1000.0; @@ -534,7 +534,7 @@ mod dtmb5415_tests { let calc = HydrostaticsCalculator::new(&vessel, 1025.0); // At draft 6.15m, volume should be ~8424 m³ (SIMMAN 2008) - let state = calc.calculate_at_draft(6.15, 0.0, 0.0, Some(VCG)); + let state = calc.from_draft(6.15, 0.0, 0.0, Some(VCG)); assert!( state.is_some(),