For the complete documentation index, see llms.txt. This page is also available as Markdown.

Corrosion & electrochemical materials

Corrosion rate and cathodic protection, electrochemical cell and battery equations, classical electrostatics and magnetism, metallurgical strengthening/hardenability, and semiconductor device physics

Six small, independent modules for the electrical and chemical behavior of materials: how fast a metal corrodes and how to slow it down (corrosion), the electrochemical reactions underneath batteries and electroplating (electrochemistry), the classical field equations for static charge (electrostatics) and moving charge (magnetism), the strengthening/hardenability/fatigue equations metallurgists use to process and specify alloys (metallurgy), and the carrier-transport and junction equations that describe a diode or MOSFET (semiconductors). Every function is a pure Float64 -> Float64 formula — no shared state, no Python, and (with one exception noted below) no struct crossing a module boundary. Two of the six modules independently implement the Nernst equation and a "corrosion rate" calculation, using different physics and different guard behavior — covered in detail below rather than glossed over.

from algenta import MojoRuntime

runtime = MojoRuntime(mode="local")

# ASTM G1 weight-loss method: a coupon lost 500 mg of mass over one week (168 h) of exposure
runtime.execute("corrosion", "corrosion_rate_mpy", [500.0, 7.86, 10.0, 168.0])["result"]
# 20.219920029080326 — mils per year, from a 7.86 g/cm^3 (steel), 10 in^2 coupon

# Faraday's law: a 2 A plating current run for one hour onto a copper (63.55 g/mol, n=2) cathode
runtime.execute("electrochemistry", "faraday_mass_deposited", [63.55, 2.0, 3600.0, 2.0])["result"]
# 2.3711457739545008 — grams of copper deposited

The modules

Module
What it does

corrosion

weight-loss corrosion rate (mpy and mm/yr), pitting rate and pitting factor, galvanic potential difference, a Nernst potential, a Tafel overpotential, passivation current density, cathodic-protection current, and remaining-wall-thickness/service-life estimation

electrochemistry

Nernst cell potential, Faraday electrolysis mass, battery capacity and gravimetric energy density, a second (current-density-based) corrosion rate, Tafel slope, Butler-Volmer exchange current density, charge-transfer resistance, diffusion-limiting current, and coulombic efficiency

electrostatics

Coulomb's law, point-charge field and potential, parallel-plate capacitance and stored energy, electric flux, surface charge density, dielectric polarization, dielectric breakdown voltage, and the method-of-images force on a charge above a grounded conductor

magnetism

Biot-Savart field from an infinite wire, force on a current-carrying wire, solenoid and toroid field, magnetic flux, solenoid inductance, magnetic field energy density, cyclotron radius, Larmor frequency, and Hall voltage

metallurgy

Hall-Petch grain-size strengthening, Johnson-Cook flow stress, Brinell-to-tensile conversion, IIW weld carbon equivalent, Jominy end-quench hardenability decay, Avrami (JMAK) phase-transformation fraction, Grossmann quench severity, Hollomon-Jaffe tempering parameter, alloy density by rule of mixtures, Wiedemann-Franz thermal conductivity, strain-hardening exponent from two stress-strain points, and Basquin fatigue life

semiconductors

intrinsic carrier concentration, drift velocity, resistivity/conductivity, depletion width and built-in potential, thermal voltage, Shockley diode current, MOSFET saturation current, junction capacitance, minority-carrier diffusion length, and Varshni band-gap-vs-temperature

Two independent Nernst equations, with different guard postures

corrosion.mojo's own docstring lists "electrochemistry (Nernst, Tafel)" among what it provides — the overlap with the electrochemistry module is by design, not an accident, and the two implementations diverge in a way worth knowing before calling either:

  • corrosion.nernst_potential(e0, n, q_ratio, temperature) computes E = E0 - (RT/nF)*ln(Q) directly, with no guard clauses at all. Pass n = 0.0 or a non-positive q_ratio and the division and log() propagate inf/nan straight through to the return value.

  • electrochemistry.nernst_cell_potential(e0, n_electrons, temperature, reaction_quotient) computes the identical formula but checks n_electrons <= 0.0 or reaction_quotient <= 0.0 or temperature <= 0.0 first and returns e0 unchanged rather than a non-finite result.

The two signatures also order their last two arguments differently — corrosion takes (q_ratio, temperature), electrochemistry takes (temperature, reaction_quotient) — so porting a call from one module to the other by position rather than by name silently swaps the reaction quotient and the temperature:

Two corrosion_rate functions, two different physical routes

Both modules also expose a function that returns a corrosion rate in mils per year, and neither is a wrapper around the other:

  • corrosion.corrosion_rate_mpy(weight_loss, density, area, time_hours) is the ASTM G1 weight-loss method: CR = 534*W / (D*A*T). It needs no electrochemical data at all — just a mass loss, a density, an area, and an exposure time.

  • electrochemistry.corrosion_rate(current_density, molar_mass, density, n_electrons) derives the same physical quantity from Faraday's law applied to a measured corrosion current density: CR = (i*M / (n*F*rho)) * 1.288e5.

Nothing in either function checks the two answers against each other — that reconciliation (a current density implies a mass loss rate through Faraday's law, and vice versa) is a real electrochemical relationship, but it lives in the caller's head, not in either kernel.

corrosion_rate_mpy and corrosion_rate_mmpy don't share an area unit

Within corrosion itself, the two weight-loss functions use the same 534/87600 ASTM constants that convert the same physical formula to different output units, but their docstrings specify different area units: corrosion_rate_mpy's area is in in², corrosion_rate_mmpy's area is in cm². Neither function converts or validates the unit — passing the same in² measurement to both, expecting the mm/yr answer to just be a unit-converted version of the mpy one, produces a number that is wrong by the roughly 6.45x ratio between the two area units on top of the intended 534-vs-87600 scaling, with no error raised:

Worked examples

Every number below was hand-derived from the real formula in the module's .mojo source and independently checked with a plain Python calculation (see Verification) — none are lifted from the module's own test file, which pins nothing.

Corrosion — galvanic couple, Tafel overpotential, and service life. Standard reduction potentials for iron (-0.44 V) and copper (+0.34 V) give the classic galvanic driving force between the two metals; a small Tafel slope example and a wall-thickness projection chain follow the same module's remaining functions:

Electrochemistry — batteries and Butler-Volmer kinetics. A 2 A, 5-hour discharge and a nominal 3.7 V, 0.5 kg lithium cell, followed by an exchange current density and the charge-transfer resistance it implies:

Electrostatics — Coulomb's law and a parallel-plate capacitor. Two 1 microcoulomb charges one meter apart, and a 1 cm² plate pair 1 mm apart in air:

Magnetism — a solenoid field and an electron's cyclotron motion. A 500-turn, 20 cm solenoid carrying 2 A, and a 1 MeV-scale electron in a 0.01 T field:

Metallurgy — Hall-Petch strengthening and fatigue life. A 10 micron grain size in a mild steel (sigma0 = 70 MPa, ky = 0.74 MPa*sqrt(m)) and a Basquin fatigue-life projection:

Semiconductors — the Shockley diode equation and Varshni's band gap. A silicon-like junction at 300 K (kT/q = 25.85 mV) and the real Varshni fit constants for silicon's temperature-dependent band gap (Eg0 = 1.166 eV, alpha = 4.73e-4 eV/K, beta = 636 K):

resistivity and conductivity are exact reciprocals of the same three inputs (1/(n*q*mobility) and n*q*mobility) — for any shared n, q, mobility, their product is 1.0 to floating-point precision, which is a useful self-check if you call both.

electrostatics.electric_flux and magnetism.magnetic_flux share the identical shape. Both compute field * area * cos(angle_rad) — the geometric definition of flux through a tilted surface doesn't care whether the field is electric or magnetic, and neither function has a guard clause, since the formula has no division or logarithm to protect.

Guarantees and refusal behavior

The six modules do not share a uniform guard policy — knowing which one you're calling matters:

  • corrosion has no guard clauses anywhere in the file. Every function is the bare formula; a zero time_hours, area, or avg_penetration produces inf, and a non-positive q_ratio in nernst_potential produces nan — both propagate silently to the caller rather than being intercepted.

  • electrochemistry guards nine of its ten functions (every one with a division or a logarithm) and returns a defined fallback — 0.0, or e0 for nernst_cell_potential — instead of a non-finite value. Only battery_capacity (a bare multiply) has nothing to guard.

  • electrostatics and magnetism guard every function that divides, returning 0.0 for a non-positive distance, separation, length, radius, carrier density, or thickness. The two flux functions and magnetic_force_wire/magnetic_energy_density/dielectric_polarization have no guards because their formulas have nothing to divide by.

  • metallurgy guards the functions built on log, exp, or a ratio — hall_petch, johnson_cook_flow_stress, avrami_fraction, tempering_parameter, strain_hardening_exponent_from_points, and fatigue_life_basquin all return a defined fallback (often the first argument, unchanged) rather than a non-finite result. carbon_equivalent, quench_severity, density_of_alloy_rule_of_mixtures, and thermal_conductivity_wiedemann_franz are unguarded linear combinations with nothing to protect.

  • semiconductors is the least consistently guarded of the six. Only junction_capacitance checks its inputs — and thoroughly, per its own source comment: it refuses a zero vbi, a non-positive 1 - v/vbi base (which would otherwise take log of a non-positive number), and a zero result from exp(m*log(base)) before dividing by it, "since inf/nan cannot cross the result wire." resistivity, depletion_width, built_in_potential, thermal_voltage, and diode_current all divide or take a logarithm with no equivalent check — a zero mobility, q, or ni reaches the caller as inf or nan.

None of the functions in this family take a List or index into one, so unlike the tensor and ML kernel families elsewhere in this catalog, there is no bounds-check-abort risk here — the worst case for an unguarded call is a non-finite Float64, never a killed worker.

Verification

All six mojo/tests/libs/test_*.mojo files behind this family (test_corrosion.mojo, test_electrochemistry.mojo, test_electrostatics.mojo, test_magnetism.mojo, test_metallurgy.mojo, test_semiconductors.mojo) are auto-generated coverage smoke tests: each calls every function once on a shared, arbitrary 0.5-valued fixture and checks only that the result is finite — none pin a specific numeric output. Every worked example above was instead hand-derived from the real formula in mojo/engine/corrosion.mojo, electrochemistry.mojo, electrostatics.mojo, magnetism.mojo, metallurgy.mojo, and semiconductors.mojo, and cross-checked with an independent plain-Python calculation of the same formula before being written down here — the silicon band-gap and room-temperature thermal-voltage examples additionally match well-known textbook reference values (~1.12 eV, ~25.85 mV) as a further check that the formulas and constants are wired correctly.

Next: see Runtime library catalog for every module and function in the full contract, or Engine catalog for how these kernel modules are grouped and served as API-level engines.

Last updated

Was this helpful?