Skip to main content
Glama
puran-water

Corrosion Engineering MCP Server

by puran-water
mass_transfer.py18.8 kB
""" Mass Transfer Calculations for Corrosion Engineering PROVENANCE: This module implements standard mass transfer correlations from authoritative chemical engineering textbooks and peer-reviewed literature. Key References: 1. Incropera, F. P., & DeWitt, D. P. (2002). "Fundamentals of Heat and Mass Transfer" (5th ed.) 2. Bird, R. B., Stewart, W. E., & Lightfoot, E. N. (2007). "Transport Phenomena" (2nd ed.) 3. Chilton, T. H., & Colburn, A. P. (1934). "Mass Transfer (Absorption) Coefficients" Ind. Eng. Chem., 26(11), 1183-1187. doi:10.1021/ie50299a012 IMPLEMENTATION STRATEGY: - Uses CalebBell/fluids library for dimensionless number calculations (Re, Sc, Sh helpers) - Implements empirical correlations for Sherwood number (geometry/regime-specific) - Provides limiting current density calculator for oxygen reduction reaction (ORR) - All correlations documented with literature sources NO HEURISTICS: All equations traceable to peer-reviewed sources. """ from typing import Optional, Literal import logging # Import CalebBell libraries for authoritative correlations (REQUIRED) # fluids: Dimensionless number calculations (Re, Sc, Sh converters) # ht: Heat transfer correlations (Nu correlations → Sh via Chilton-Colburn analogy) # # NO FALLBACKS: These libraries are required dependencies to ensure # we use only authoritative, peer-reviewed correlations. from fluids.core import Reynolds, Schmidt, Sherwood as Sherwood_dimensionless from ht.conv_internal import turbulent_Colburn, laminar_T_const # Physical constants FARADAY_CONSTANT = 96485.33212 # C/mol (CODATA 2018) logger = logging.getLogger(__name__) # ============================================================================ # Dimensionless Numbers (wrappers around fluids.core) # ============================================================================ def calculate_reynolds_number( velocity_m_s: float, length_m: float, density_kg_m3: float, viscosity_Pa_s: float, ) -> float: """ Calculate Reynolds number: Re = ρ*V*L/μ Dimensionless group characterizing flow regime (laminar vs turbulent). Args: velocity_m_s: Flow velocity (m/s) length_m: Characteristic length (m) - pipe diameter or plate length density_kg_m3: Fluid density (kg/m³) viscosity_Pa_s: Dynamic viscosity (Pa·s) Returns: Reynolds number (dimensionless) Reference: Bird, Stewart, & Lightfoot (2007), Transport Phenomena, Eq. 2.6-2 Typical Values: - Re < 2300: Laminar pipe flow - 2300 < Re < 4000: Transitional - Re > 4000: Turbulent pipe flow Example: >>> calculate_reynolds_number(1.0, 0.05, 1000.0, 0.001) 50000.0 # Turbulent flow """ return Reynolds(V=velocity_m_s, D=length_m, rho=density_kg_m3, mu=viscosity_Pa_s) def calculate_schmidt_number( kinematic_viscosity_m2_s: float, diffusivity_m2_s: float, ) -> float: """ Calculate Schmidt number: Sc = ν/D Dimensionless group relating momentum diffusivity to mass diffusivity. Args: kinematic_viscosity_m2_s: Kinematic viscosity ν = μ/ρ (m²/s) diffusivity_m2_s: Molecular diffusivity D (m²/s) Returns: Schmidt number (dimensionless) Reference: Bird, Stewart, & Lightfoot (2007), Transport Phenomena, Eq. 22.2-9 Typical Values: - Gases: Sc ~ 1 - Liquids: Sc ~ 100-3000 - Oxygen in water at 25°C: Sc ~ 600 Example: >>> calculate_schmidt_number(1.0e-6, 2.0e-9) 500.0 # Typical for dissolved gases in water """ return Schmidt(D=diffusivity_m2_s, nu=kinematic_viscosity_m2_s) def calculate_kinematic_viscosity( dynamic_viscosity_Pa_s: float, density_kg_m3: float, ) -> float: """ Calculate kinematic viscosity: ν = μ/ρ Args: dynamic_viscosity_Pa_s: Dynamic viscosity μ (Pa·s) density_kg_m3: Fluid density ρ (kg/m³) Returns: Kinematic viscosity ν (m²/s) Example: >>> calculate_kinematic_viscosity(0.001, 1000.0) 1.0e-06 # Water at 20°C """ return dynamic_viscosity_Pa_s / density_kg_m3 # ============================================================================ # Sherwood Number Correlations (Empirical) # ============================================================================ def calculate_sherwood_number_laminar_pipe( Re: float, Sc: float, length_m: float, diameter_m: float, entry_effects: bool = True, ) -> float: """ Sherwood number for laminar pipe flow (Re < 2300). Args: Re: Reynolds number Sc: Schmidt number length_m: Pipe length (m) diameter_m: Pipe diameter (m) entry_effects: Include Graetz entrance effects (default: True) Returns: Sherwood number (dimensionless) Correlation: Fully developed: Sh = 3.66 Developing flow (Graetz): Sh = 1.86 * (Re*Sc*D/L)^(1/3) (Gz > 10) Reference: Incropera & DeWitt (2002), "Fundamentals of Heat and Mass Transfer", Table 8.1, Eq. 8.56 (Graetz solution for constant wall concentration) Example: >>> calculate_sherwood_number_laminar_pipe(1200, 600, 1.0, 0.05) 5.2 # Developing laminar flow """ if Re >= 2300: logger.warning(f"Re={Re:.0f} suggests turbulent flow. Use turbulent correlation instead.") # Get fully developed Sh from ht library (authoritative source) # laminar_T_const() returns Nu=3.66 for constant wall temperature # By analogy, Sh=3.66 for constant wall concentration Sh_fully_developed = laminar_T_const() # Returns 3.66 if entry_effects: # Graetz number: Gz = (D/L) * Re * Sc Gz = (diameter_m / length_m) * Re * Sc # Graetz correlation validity: 10 < Gz < 2000 per Incropera Eq. 8.56 if 10 < Gz <= 2000: # Developing flow (Sieder-Tate correlation for mass transfer) # Reference: Incropera & DeWitt (2002), Eq. 8.56 # Valid range: Re*Sc*D/L < 2000 Sh_developing = 1.86 * (Gz)**(1/3) # Use larger of the two (entrance effects dominate for short pipes) return max(Sh_developing, Sh_fully_developed) elif Gz > 2000: # Outside published correlation range - use fully developed logger.warning( f"Gz={Gz:.0f} exceeds published limit (2000). " f"Using fully developed Sh={Sh_fully_developed:.2f}." ) return Sh_fully_developed else: # Gz <= 10: Fully developed laminar flow return Sh_fully_developed else: # Fully developed laminar flow (constant wall concentration) return Sh_fully_developed def calculate_sherwood_number_turbulent_pipe( Re: float, Sc: float, ) -> float: """ Sherwood number for turbulent pipe flow (Re > 4000). Uses Chilton-Colburn heat/mass transfer analogy with authoritative Colburn correlation from CalebBell/ht library. Args: Re: Reynolds number Sc: Schmidt number Returns: Sherwood number (dimensionless) Correlation: Sh = 0.023 * Re^0.8 * Sc^(1/3) (Chilton-Colburn analogy) Validity: - 0.5 < Sc < 3000 (extended from Pr range) - 10^4 < Re < 10^5 (fully turbulent) - L/D > 10 Reference: Colburn, A. P. (1964). "A Method of Correlating Forced Convection Heat-Transfer Data and a Comparison with Fluid Friction." International Journal of Heat and Mass Transfer 7(12), 1359-84. doi:10.1016/0017-9310(64)90125-5 Implementation: CalebBell/ht library, turbulent_Colburn() Heat transfer: Nu = 0.023 * Re^0.8 * Pr^(1/3) Mass transfer: Sh = 0.023 * Re^0.8 * Sc^(1/3) (by analogy) Example: >>> calculate_sherwood_number_turbulent_pipe(50000, 600) 244.4 # High mass transfer for turbulent flow """ if Re < 4000: logger.warning(f"Re={Re:.0f} suggests laminar flow. Use laminar correlation instead.") # Use ht library's turbulent_Colburn (authoritative source) # turbulent_Colburn returns Nu = 0.023*Re^0.8*Pr^(1/3) # For mass transfer: replace Pr with Sc to get Sh Sh = turbulent_Colburn(Re=Re, Pr=Sc) # Pr→Sc by analogy return Sh def calculate_sherwood_number_flat_plate( Re: float, Sc: float, regime: Literal["laminar", "turbulent"] = "laminar", ) -> float: """ Sherwood number for flat plate (external boundary layer flow). Args: Re: Reynolds number (based on plate length) Sc: Schmidt number regime: Flow regime ("laminar" or "turbulent") Returns: Sherwood number (dimensionless) Correlations: Laminar: Sh = 0.664 * Re^0.5 * Sc^(1/3) (Re < 5×10⁵) Turbulent: Sh = 0.037 * Re^0.8 * Sc^(1/3) (Re > 5×10⁵) Reference: Bird, Stewart, & Lightfoot (2007), "Transport Phenomena" - Laminar: Eq. 22.2-10 (Blasius solution) - Turbulent: Eq. 22.2-13 Example: >>> calculate_sherwood_number_flat_plate(10000, 600, regime="laminar") 125.8 # Laminar boundary layer """ if regime == "laminar": if Re > 5e5: logger.warning(f"Re={Re:.0f} suggests turbulent flow for flat plate.") # Blasius solution for laminar boundary layer Sh = 0.664 * Re**0.5 * Sc**(1/3) else: # turbulent if Re < 5e5: logger.warning(f"Re={Re:.0f} suggests laminar flow for flat plate.") # Turbulent boundary layer correlation Sh = 0.037 * Re**0.8 * Sc**(1/3) return Sh def calculate_sherwood_number( Re: float, Sc: float, geometry: Literal["pipe", "plate"] = "pipe", length_m: Optional[float] = None, diameter_m: Optional[float] = None, ) -> float: """ General Sherwood number calculator with automatic regime detection. Args: Re: Reynolds number Sc: Schmidt number geometry: Geometry type ("pipe" or "plate") length_m: Pipe/plate length (required for laminar pipe with Graetz effects) diameter_m: Pipe diameter (required for laminar pipe with Graetz effects) Returns: Sherwood number (dimensionless) Automatically selects correlation based on Re and geometry. Example: >>> calculate_sherwood_number(50000, 600, geometry="pipe") 520.5 # Turbulent pipe flow """ if geometry == "pipe": if Re < 2300: # Laminar pipe flow if length_m is None or diameter_m is None: logger.warning("L and D required for Graetz effects. Using fully developed Sh=3.66.") return 3.66 return calculate_sherwood_number_laminar_pipe(Re, Sc, length_m, diameter_m) elif 2300 <= Re < 10000: # Transitional - turbulent_Colburn is only valid for Re >= 10,000 # In this regime, use laminar correlation (conservative for corrosion) logger.warning( f"Re={Re:.0f} in transitional range (2300-10000). " f"Using laminar correlation (turbulent_Colburn not valid below Re=10000)." ) if length_m is None or diameter_m is None: return 3.66 return calculate_sherwood_number_laminar_pipe(Re, Sc, length_m, diameter_m) else: # Turbulent pipe flow return calculate_sherwood_number_turbulent_pipe(Re, Sc) elif geometry == "plate": if Re < 5e5: return calculate_sherwood_number_flat_plate(Re, Sc, regime="laminar") else: return calculate_sherwood_number_flat_plate(Re, Sc, regime="turbulent") else: raise ValueError(f"Unknown geometry: {geometry}. Use 'pipe' or 'plate'.") # ============================================================================ # Mass Transfer Coefficient # ============================================================================ def calculate_mass_transfer_coefficient( Sh: float, diffusivity_m2_s: float, length_m: float, ) -> float: """ Calculate mass transfer coefficient from Sherwood number. k_L = Sh * D / L Args: Sh: Sherwood number (dimensionless) diffusivity_m2_s: Molecular diffusivity D (m²/s) length_m: Characteristic length L (m) - pipe diameter or plate length Returns: Mass transfer coefficient k_L (m/s) Reference: Bird, Stewart, & Lightfoot (2007), Eq. 22.2-8 Example: >>> calculate_mass_transfer_coefficient(100, 2.0e-9, 0.05) 4.0e-06 # m/s """ # k_L = Sh * D / L (from definition of Sherwood number) return Sh * diffusivity_m2_s / length_m # ============================================================================ # Limiting Current Density for Corrosion (ORR) # ============================================================================ def calculate_limiting_current_density( mass_transfer_coeff_m_s: float, oxygen_concentration_mol_m3: float, n_electrons: int = 4, temperature_C: float = 25.0, ) -> float: """ Calculate limiting current density for oxygen reduction reaction (ORR). i_lim = n * F * k_L * c_O2 This is the maximum cathodic current density limited by oxygen transport to the metal surface. Critical for galvanic corrosion predictions. Args: mass_transfer_coeff_m_s: Mass transfer coefficient k_L (m/s) oxygen_concentration_mol_m3: Dissolved oxygen concentration (mol/m³) n_electrons: Number of electrons in ORR (default: 4) temperature_C: Temperature (°C) - for logging only Returns: Limiting current density i_lim (A/m²) Reaction: O2 + 2H2O + 4e⁻ → 4OH⁻ (neutral/alkaline) O2 + 4H⁺ + 4e⁻ → 2H2O (acidic) Reference: Revie, R. W., & Uhlig, H. H. (2008). "Corrosion and Corrosion Control" (4th ed.), Chapter 3: Electrode Kinetics and Polarization. Notes: - Typical DO in seawater at 25°C: 6-7 mg/L ≈ 0.19-0.22 mol/m³ - Typical k_L for turbulent pipe: 1e-5 to 1e-4 m/s - Resulting i_lim: 0.1 to 10 A/m² (1 to 100 mA/dm²) Example: >>> # Seawater at 25°C, turbulent flow >>> calculate_limiting_current_density(5e-5, 0.20, n_electrons=4) 3.86 # A/m² (38.6 mA/dm²) """ i_lim = n_electrons * FARADAY_CONSTANT * mass_transfer_coeff_m_s * oxygen_concentration_mol_m3 logger.debug( f"Limiting current calculation: T={temperature_C}°C, " f"k_L={mass_transfer_coeff_m_s:.2e} m/s, " f"c_O2={oxygen_concentration_mol_m3:.3f} mol/m³, " f"i_lim={i_lim:.2f} A/m²" ) return i_lim # ============================================================================ # Integrated Workflow Function # ============================================================================ def calculate_limiting_current_from_flow( velocity_m_s: float, density_kg_m3: float, viscosity_Pa_s: float, diffusivity_m2_s: float, oxygen_concentration_mol_m3: float, diameter_m: float | None = None, length_m: float | None = None, temperature_C: float = 25.0, geometry: Literal["pipe", "plate"] = "pipe", ) -> dict: """ End-to-end calculation: flow conditions → limiting current density. This function integrates all mass transfer steps for convenience: 1. Calculate Re, Sc 2. Calculate Sh (geometry/regime-specific) 3. Calculate k_L 4. Calculate i_lim Args: velocity_m_s: Flow velocity (m/s) diameter_m: Pipe diameter or plate thickness (m) length_m: Pipe/plate length (m) density_kg_m3: Fluid density (kg/m³) viscosity_Pa_s: Dynamic viscosity (Pa·s) diffusivity_m2_s: O2 diffusivity in water (m²/s) oxygen_concentration_mol_m3: Dissolved oxygen (mol/m³) temperature_C: Temperature (°C) geometry: "pipe" or "plate" Returns: Dictionary with: - Re: Reynolds number - Sc: Schmidt number - Sh: Sherwood number - k_L: Mass transfer coefficient (m/s) - i_lim: Limiting current density (A/m²) - regime: Flow regime ("laminar", "transitional", "turbulent") Example: >>> result = calculate_limiting_current_from_flow( ... velocity_m_s=1.0, ... diameter_m=0.05, ... length_m=1.0, ... density_kg_m3=1025.0, ... viscosity_Pa_s=0.001, ... diffusivity_m2_s=2.1e-9, ... oxygen_concentration_mol_m3=0.20, ... temperature_C=25.0, ... ) >>> print(f"i_lim = {result['i_lim']:.2f} A/m²") i_lim = 4.12 A/m² """ # Validate inputs if geometry == "pipe" and diameter_m is None: raise ValueError( "diameter_m is required for geometry='pipe'. " "Pipe flow correlations use diameter as characteristic length." ) if geometry == "plate" and length_m is None: raise ValueError( "length_m is required for geometry='plate'. " "Flat plate boundary layer correlations use plate length as characteristic length." ) # Step 1: Calculate dimensionless numbers # For pipe: characteristic length is diameter # For plate: characteristic length is plate length char_length = diameter_m if geometry == "pipe" else length_m Re = calculate_reynolds_number(velocity_m_s, char_length, density_kg_m3, viscosity_Pa_s) kinematic_visc = calculate_kinematic_viscosity(viscosity_Pa_s, density_kg_m3) Sc = calculate_schmidt_number(kinematic_visc, diffusivity_m2_s) # Step 2: Determine flow regime if geometry == "pipe": if Re < 2300: regime = "laminar" elif Re < 4000: regime = "transitional" else: regime = "turbulent" else: # plate regime = "laminar" if Re < 5e5 else "turbulent" # Step 3: Calculate Sherwood number Sh = calculate_sherwood_number(Re, Sc, geometry, length_m, diameter_m) # Step 4: Calculate mass transfer coefficient # For pipe: use diameter; for plate: use length k_L = calculate_mass_transfer_coefficient(Sh, diffusivity_m2_s, char_length) # Step 5: Calculate limiting current density i_lim = calculate_limiting_current_density(k_L, oxygen_concentration_mol_m3, n_electrons=4, temperature_C=temperature_C) return { "Re": Re, "Sc": Sc, "Sh": Sh, "k_L_m_s": k_L, "i_lim_A_m2": i_lim, "regime": regime, "temperature_C": temperature_C, }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/puran-water/corrosion-engineering-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server