Skip to main content
Glama
puran-water

Corrosion Engineering MCP Server

by puran-water
nrl_galvanic_series.py7.54 kB
""" NRL Seawater Galvanic Series Data Loader DIRECT IMPORT from: US Naval Research Laboratory Source: corrosion-modeling-applications/cma/SeawaterPotentialData.xml License: Public Domain (US Government work) Standard Reference: - ASTM G82-98 (2014): "Standard Guide for Development and Use of a Galvanic Series for Predicting Galvanic Corrosion Performance" All potentials are measured vs. Saturated Calomel Electrode (SCE) reference. Per ASTM G3 and NACE SP0208: E(SHE) = E(SCE) + 0.241V Data Source: NRL's corrosion modeling applications XML database """ import xml.etree.ElementTree as ET from pathlib import Path from typing import Dict, Optional, Tuple from dataclasses import dataclass # Location of NRL XML file (consolidated to external/nrl_coefficients/) NRL_XML_PATH = Path(__file__).parent.parent / "external" / "nrl_coefficients" / "SeawaterPotentialData.xml" # Reference electrode conversion constant (per ASTM G3-14) E_SCE_TO_SHE = 0.241 # V @dataclass class GalvanicSeriesEntry: """ Entry in the galvanic series. Attributes: name: Material name from ASTM G82 / NRL database potential_sce: Corrosion potential vs SCE, V potential_she: Corrosion potential vs SHE, V (calculated) activity_category: NRL activity category (A-T) """ name: str potential_sce: float potential_she: float activity_category: Optional[str] = None def load_galvanic_series_xml() -> Dict[str, GalvanicSeriesEntry]: """ Load galvanic series data from NRL XML file. Returns: Dictionary mapping material name (lowercase, normalized) to GalvanicSeriesEntry Raises: FileNotFoundError: If XML file not found ET.ParseError: If XML is malformed Example: >>> data = load_galvanic_series_xml() >>> carbon_steel = data["carbon steel"] >>> carbon_steel.potential_sce -0.61 >>> carbon_steel.potential_she -0.369 # -0.61 + 0.241 """ if not NRL_XML_PATH.exists(): raise FileNotFoundError( f"NRL galvanic series XML not found at {NRL_XML_PATH}. " f"Expected file: SeawaterPotentialData.xml" ) tree = ET.parse(NRL_XML_PATH) root = tree.getroot() galvanic_series = {} for data_elem in root.findall("Data"): name_elem = data_elem.find("Name") potential_elem = data_elem.find("PotentialValue") category_elem = data_elem.find("ActivityCategory") if name_elem is None or potential_elem is None: continue # Skip incomplete entries name = name_elem.text.strip() potential_sce = float(potential_elem.text) activity_category = category_elem.text if category_elem is not None else None # Convert SCE to SHE potential_she = potential_sce + E_SCE_TO_SHE entry = GalvanicSeriesEntry( name=name, potential_sce=potential_sce, potential_she=potential_she, activity_category=activity_category ) # Store with normalized key (lowercase, no special chars) key_normalized = name.lower().replace("-", " ").replace(",", "") galvanic_series[key_normalized] = entry return galvanic_series # Cache for loaded data _GALVANIC_SERIES_CACHE: Optional[Dict[str, GalvanicSeriesEntry]] = None def get_galvanic_potential( material: str, reference: str = "SHE" ) -> Optional[float]: """ Get galvanic potential for a material from NRL database. Args: material: Material name (case-insensitive, fuzzy matching) reference: Reference electrode ("SHE" or "SCE") Returns: Corrosion potential in volts vs specified reference, or None if not found Example: >>> get_galvanic_potential("Carbon Steel", "SCE") -0.61 >>> get_galvanic_potential("Carbon Steel", "SHE") -0.369 >>> get_galvanic_potential("316L passive") # Fuzzy match -0.05 + 0.241 = 0.191 """ global _GALVANIC_SERIES_CACHE if _GALVANIC_SERIES_CACHE is None: _GALVANIC_SERIES_CACHE = load_galvanic_series_xml() # Normalize material name material_normalized = material.lower().replace("-", " ").replace(",", "").replace("_", " ") # Try exact match if material_normalized in _GALVANIC_SERIES_CACHE: entry = _GALVANIC_SERIES_CACHE[material_normalized] return entry.potential_she if reference == "SHE" else entry.potential_sce # Try fuzzy match (contains or is contained by) for key, entry in _GALVANIC_SERIES_CACHE.items(): if material_normalized in key or key in material_normalized: return entry.potential_she if reference == "SHE" else entry.potential_sce # Try match on specific keywords keywords_to_try = [ # Stainless steels ("316", "stainless steel type 316 passive"), ("304", "stainless steel type 304 passive"), ("430", "stainless steel type 430 passive"), ("410", "stainless steel type 410 active"), # Carbon steels ("carbon", "carbon steel"), ("steel", "carbon steel"), # Aluminum ("aluminum", "aa 6061 t"), ("al", "aa 6061 t"), # Copper alloys ("copper", "copper"), ("brass", "copper alloy 230 (red brass)"), ("bronze", "copper alloy 954 (aluminum bronze)"), # Nickel alloys ("inconel", "nickel alloy 600 (ni cr)"), ("monel", "nickel alloy 400 (monel 400)"), ("hastelloy", "nickel alloy 276 (hastelloy c)"), # Titanium ("titanium", "titanium (solid)"), ("ti", "titanium (solid)"), # Others ("zinc", "zinc"), ("lead", "lead"), ("magnesium", "magnesium"), ] for keyword, target_key in keywords_to_try: if keyword in material_normalized: entry = _GALVANIC_SERIES_CACHE.get(target_key) if entry: return entry.potential_she if reference == "SHE" else entry.potential_sce return None # Not found def get_galvanic_series_entry(material: str) -> Optional[GalvanicSeriesEntry]: """ Get full galvanic series entry for a material. Args: material: Material name (case-insensitive, fuzzy matching) Returns: GalvanicSeriesEntry with all data, or None if not found """ global _GALVANIC_SERIES_CACHE if _GALVANIC_SERIES_CACHE is None: _GALVANIC_SERIES_CACHE = load_galvanic_series_xml() material_normalized = material.lower().replace("-", " ").replace(",", "").replace("_", " ") # Try exact match if material_normalized in _GALVANIC_SERIES_CACHE: return _GALVANIC_SERIES_CACHE[material_normalized] # Try fuzzy match for key, entry in _GALVANIC_SERIES_CACHE.items(): if material_normalized in key or key in material_normalized: return entry return None def list_available_materials() -> list[str]: """ List all materials available in the NRL galvanic series database. Returns: Sorted list of material names """ global _GALVANIC_SERIES_CACHE if _GALVANIC_SERIES_CACHE is None: _GALVANIC_SERIES_CACHE = load_galvanic_series_xml() return sorted([entry.name for entry in _GALVANIC_SERIES_CACHE.values()]) __all__ = [ "GalvanicSeriesEntry", "load_galvanic_series_xml", "get_galvanic_potential", "get_galvanic_series_entry", "list_available_materials", "E_SCE_TO_SHE", ]

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