Skip to the content.

pH Stabilization and Corrosion Control

This guide covers the use of NeqSim’s Electrolyte CPA equation of state (Statoil model) for modeling pH stabilization, CO2 corrosion control, and protective FeCO3 (siderite) layer formation in oil and gas production systems.

Overview

The Corrosion Challenge

In CO2-rich production systems, carbonic acid formation leads to sweet corrosion:

\[\text{CO}_2 + \text{H}_2\text{O} \leftrightarrow \text{H}_2\text{CO}_3 \leftrightarrow \text{H}^+ + \text{HCO}_3^-\] \[\text{Fe} \rightarrow \text{Fe}^{2+} + 2e^-\]

This can cause severe pipeline corrosion rates exceeding 10 mm/year without mitigation.

pH Stabilization Strategy

pH stabilization uses alkaline chemicals (typically NaOH) to:

  1. Raise the pH of the aqueous phase above 6.0-6.5
  2. Promote formation of a protective FeCO3 (siderite) layer on steel surfaces
  3. Reduce the rate of iron dissolution

The protective siderite layer forms when:

\[\text{Fe}^{2+} + \text{CO}_3^{2-} \rightarrow \text{FeCO}_3 \downarrow\]

This layer acts as a barrier, reducing corrosion rates by 1-2 orders of magnitude.


Electrolyte CPA Equation of State

NeqSim’s Electrolyte CPA EoS (Statoil model) is specifically designed for aqueous electrolyte systems with dissolved gases. It combines:

Key Capabilities

Feature Description
pH calculation From ionic equilibria
Ion speciation CO2/HCO3-/CO3– equilibrium
Scale potential Saturation ratio for FeCO3, CaCO3, etc.
Gas solubility CO2, H2S in brines
Activity coefficients Debye-Hückel/Pitzer-type

Basic pH Calculation

Java Example

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class PHCalculation {
    public static void main(String[] args) {
        
        // Create electrolyte system at reservoir conditions
        SystemElectrolyteCPAstatoil fluid = 
            new SystemElectrolyteCPAstatoil(353.15, 50.0);  // 80°C, 50 bara
        
        // Add gas components
        fluid.addComponent("methane", 0.85);
        fluid.addComponent("CO2", 0.03);      // 3 mol% CO2
        fluid.addComponent("H2S", 0.001);     // 0.1 mol% H2S
        
        // Add water and ions (formation water composition)
        fluid.addComponent("water", 0.10);
        fluid.addComponent("Na+", 0.005);     // Sodium
        fluid.addComponent("Cl-", 0.005);     // Chloride
        fluid.addComponent("Ca++", 0.0002);   // Calcium
        fluid.addComponent("Fe++", 0.00001);  // Dissolved iron
        fluid.addComponent("HCO3-", 0.001);   // Bicarbonate
        
        // Initialize electrolyte system
        fluid.chemicalReactionInit();
        fluid.createDatabase(true);
        fluid.setMixingRule(10);  // Electrolyte CPA mixing rule
        
        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();
        fluid.init(3);
        
        // Get aqueous phase pH
        int aqPhase = fluid.getPhaseNumberOfPhase("aqueous");
        double pH = fluid.getPhase(aqPhase).getpH();
        
        System.out.printf("Calculated pH: %.2f%n", pH);
        System.out.printf("CO2 partial pressure: %.2f bara%n", 
            fluid.getComponent("CO2").getx() * fluid.getPressure());
    }
}

Python Example

from neqsim import jneqsim

# Import electrolyte CPA system
SystemElectrolyteCPAstatoil = jneqsim.thermo.system.SystemElectrolyteCPAstatoil
ThermodynamicOperations = jneqsim.thermodynamicoperations.ThermodynamicOperations

# Create fluid at 80°C, 50 bara
fluid = SystemElectrolyteCPAstatoil(273.15 + 80.0, 50.0)

# Add components
fluid.addComponent("methane", 0.85)
fluid.addComponent("CO2", 0.03)
fluid.addComponent("water", 0.10)
fluid.addComponent("Na+", 0.005)
fluid.addComponent("Cl-", 0.005)
fluid.addComponent("HCO3-", 0.001)
fluid.addComponent("Fe++", 0.00001)

# Initialize and flash
fluid.chemicalReactionInit()
fluid.createDatabase(True)
fluid.setMixingRule(10)

ops = ThermodynamicOperations(fluid)
ops.TPflash()
fluid.init(3)

# Get pH
aq_phase = fluid.getPhaseNumberOfPhase("aqueous")
pH = fluid.getPhase(aq_phase).getpH()
print(f"Aqueous phase pH: {pH:.2f}")

pH Stabilization with NaOH

Concept

Adding NaOH (sodium hydroxide) raises pH through:

\[\text{NaOH} \rightarrow \text{Na}^+ + \text{OH}^-\] \[\text{OH}^- + \text{H}^+ \rightarrow \text{H}_2\text{O}\]

This shifts carbonate equilibrium towards CO3–, promoting FeCO3 precipitation.

Calculating Required NaOH Dose

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class NaOHDosing {
    public static void main(String[] args) {
        
        double targetpH = 6.5;  // Target pH for siderite formation
        
        // Base case - production fluid without treatment
        SystemElectrolyteCPAstatoil fluid = 
            new SystemElectrolyteCPAstatoil(353.15, 50.0);
        
        // Gas and water composition
        fluid.addComponent("methane", 0.80);
        fluid.addComponent("CO2", 0.05);      // 5% CO2 - corrosive
        fluid.addComponent("water", 0.12);
        fluid.addComponent("Na+", 0.015);
        fluid.addComponent("Cl-", 0.015);
        fluid.addComponent("Fe++", 0.00002);
        fluid.addComponent("HCO3-", 0.002);
        
        fluid.chemicalReactionInit();
        fluid.createDatabase(true);
        fluid.setMixingRule(10);
        
        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();
        fluid.init(3);
        
        int aqPhase = fluid.getPhaseNumberOfPhase("aqueous");
        double basepH = fluid.getPhase(aqPhase).getpH();
        
        System.out.printf("Initial pH: %.2f%n", basepH);
        System.out.printf("Target pH: %.2f%n", targetpH);
        System.out.println();
        
        // Iterate to find NaOH dose
        System.out.println("NaOH dose (mmol/kg) | pH");
        System.out.println("--------------------|-----");
        
        for (double naohDose = 0; naohDose <= 50; naohDose += 5) {
            SystemElectrolyteCPAstatoil testFluid = 
                new SystemElectrolyteCPAstatoil(353.15, 50.0);
            
            testFluid.addComponent("methane", 0.80);
            testFluid.addComponent("CO2", 0.05);
            testFluid.addComponent("water", 0.12);
            testFluid.addComponent("Na+", 0.015 + naohDose/1000);  // Added Na+
            testFluid.addComponent("Cl-", 0.015);
            testFluid.addComponent("OH-", naohDose/1000);          // Added OH-
            testFluid.addComponent("Fe++", 0.00002);
            testFluid.addComponent("HCO3-", 0.002);
            
            testFluid.chemicalReactionInit();
            testFluid.createDatabase(true);
            testFluid.setMixingRule(10);
            
            ThermodynamicOperations testOps = new ThermodynamicOperations(testFluid);
            testOps.TPflash();
            testFluid.init(3);
            
            int testAqPhase = testFluid.getPhaseNumberOfPhase("aqueous");
            double testpH = testFluid.getPhase(testAqPhase).getpH();
            
            String marker = (testpH >= targetpH && testpH < targetpH + 0.2) ? " <-- TARGET" : "";
            System.out.printf("%18.1f | %.2f%s%n", naohDose, testpH, marker);
        }
    }
}

FeCO3 (Siderite) Protective Layer

Siderite Formation Conditions

The protective FeCO3 layer forms when the saturation ratio (SR) exceeds 1:

\[SR = \frac{[\text{Fe}^{2+}][\text{CO}_3^{2-}]}{K_{sp}}\]

Where $K_{sp}$ for siderite is approximately 10^-10.89 at 25°C.

Key Factors for Protective Layer

Factor Favorable Unfavorable
pH > 6.0 < 5.5
Temperature > 60°C < 40°C
Fe++ concentration > 1 ppm < 0.1 ppm
CO2 partial pressure Moderate Very high
Flow velocity Low/moderate > 3 m/s (erosion)

Calculating FeCO3 Saturation

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class SideriteSaturation {
    public static void main(String[] args) {
        
        SystemElectrolyteCPAstatoil fluid = 
            new SystemElectrolyteCPAstatoil(353.15, 50.0);  // 80°C
        
        // CO2-rich gas with formation water
        fluid.addComponent("methane", 0.80);
        fluid.addComponent("CO2", 0.05);
        fluid.addComponent("water", 0.12);
        fluid.addComponent("Na+", 0.02);      // With NaOH treatment
        fluid.addComponent("OH-", 0.005);     // Added OH-
        fluid.addComponent("Cl-", 0.015);
        fluid.addComponent("Fe++", 0.00005);  // 5 mg/L dissolved iron
        fluid.addComponent("HCO3-", 0.003);
        fluid.addComponent("CO3--", 0.0005);
        
        fluid.chemicalReactionInit();
        fluid.createDatabase(true);
        fluid.setMixingRule(10);
        
        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();
        fluid.init(3);
        
        // Check scale potential
        int aqPhase = fluid.getPhaseNumberOfPhase("aqueous");
        ops.checkScalePotential(aqPhase);
        
        // Get results
        String[][] results = ops.getResultTable();
        
        System.out.println("=== SCALE POTENTIAL RESULTS ===");
        System.out.println();
        System.out.println("Salt           | Saturation Ratio | Status");
        System.out.println("---------------|------------------|--------");
        
        for (int i = 1; i < results.length && results[i][0] != null && 
             !results[i][0].isEmpty(); i++) {
            
            String salt = results[i][0];
            double sr = Double.parseDouble(results[i][1]);
            String status;
            
            if (salt.equals("FeCO3")) {
                // For siderite, we WANT SR > 1 for protective layer
                if (sr > 1.0) {
                    status = "✅ Protective layer forms";
                } else if (sr > 0.5) {
                    status = "⚠️ Marginal - increase pH";
                } else {
                    status = "❌ No protection";
                }
            } else {
                // For other scales, SR > 1 is a problem
                if (sr > 1.0) {
                    status = "⚠️ SCALE RISK";
                } else {
                    status = "✅ No scaling";
                }
            }
            
            System.out.printf("%-14s | %16.3f | %s%n", salt, sr, status);
        }
        
        // Also report pH
        double pH = fluid.getPhase(aqPhase).getpH();
        System.out.println();
        System.out.printf("Aqueous phase pH: %.2f%n", pH);
    }
}

Combined Hydrate Inhibition and pH Control

MEG with pH Stabilization

In subsea systems, monoethylene glycol (MEG) is often used for both:

This is called pH-stabilized MEG or buffered MEG.

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class MEGWithPHStabilization {
    public static void main(String[] args) {
        
        // Subsea conditions
        double seabedTemp = 277.15;  // 4°C
        double pressure = 150.0;     // bara
        
        System.out.println("=== MEG + pH STABILIZATION ===");
        System.out.println();
        
        // Case 1: Without MEG or pH control
        System.out.println("Case 1: No treatment");
        analyzeCase(seabedTemp, pressure, 0.0, 0.0);
        
        // Case 2: MEG only (30 wt%)
        System.out.println("Case 2: 30% MEG only");
        analyzeCase(seabedTemp, pressure, 0.30, 0.0);
        
        // Case 3: MEG + pH stabilization
        System.out.println("Case 3: 30% MEG + NaOH (pH stabilized)");
        analyzeCase(seabedTemp, pressure, 0.30, 0.01);
    }
    
    static void analyzeCase(double T, double P, double megFrac, double naohDose) {
        
        SystemElectrolyteCPAstatoil fluid = 
            new SystemElectrolyteCPAstatoil(T, P);
        
        // Gas composition
        fluid.addComponent("methane", 0.75);
        fluid.addComponent("ethane", 0.05);
        fluid.addComponent("propane", 0.02);
        fluid.addComponent("CO2", 0.04);
        
        // Water phase with MEG
        double waterFrac = 0.12 * (1 - megFrac);
        double megMolFrac = 0.12 * megFrac * (18.0/62.0);  // Convert wt% to mol
        
        fluid.addComponent("water", waterFrac);
        if (megFrac > 0) {
            fluid.addComponent("MEG", megMolFrac);
        }
        
        // Ions
        fluid.addComponent("Na+", 0.01 + naohDose);
        fluid.addComponent("Cl-", 0.01);
        fluid.addComponent("Fe++", 0.00002);
        fluid.addComponent("HCO3-", 0.002);
        
        if (naohDose > 0) {
            fluid.addComponent("OH-", naohDose);
        }
        
        fluid.chemicalReactionInit();
        fluid.createDatabase(true);
        fluid.setMixingRule(10);
        fluid.setHydrateCheck(true);
        
        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        
        // Calculate hydrate temperature
        try {
            ops.hydrateFormationTemperature();
            double hydrateT = fluid.getTemperature() - 273.15;
            System.out.printf("  Hydrate formation T: %.1f °C%n", hydrateT);
        } catch (Exception e) {
            System.out.println("  No hydrate at these conditions");
        }
        
        // Reset and flash at operating conditions
        fluid.setTemperature(T);
        fluid.setPressure(P);
        ops.TPflash();
        fluid.init(3);
        
        // Get pH
        try {
            int aqPhase = fluid.getPhaseNumberOfPhase("aqueous");
            double pH = fluid.getPhase(aqPhase).getpH();
            System.out.printf("  Aqueous pH: %.2f%n", pH);
            
            // Check FeCO3 saturation
            ops.checkScalePotential(aqPhase);
            String[][] results = ops.getResultTable();
            
            for (int i = 1; i < results.length; i++) {
                if (results[i][0] != null && results[i][0].equals("FeCO3")) {
                    double sr = Double.parseDouble(results[i][1]);
                    String status = sr > 1.0 ? "Protected" : "Unprotected";
                    System.out.printf("  FeCO3 saturation ratio: %.3f (%s)%n", sr, status);
                    break;
                }
            }
        } catch (Exception e) {
            System.out.println("  Could not determine pH/scale");
        }
        System.out.println();
    }
}

Corrosion Rate Estimation

de Waard-Milliams Correlation

While NeqSim calculates thermodynamics, corrosion rate prediction requires empirical correlations. The de Waard-Milliams model is commonly used:

\[\log(CR) = 5.8 - \frac{1710}{T} + 0.67 \cdot \log(pCO_2)\]

Where:

Combined NeqSim + Corrosion Workflow

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;
import neqsim.thermodynamicoperations.ThermodynamicOperations;

public class CorrosionEstimate {
    public static void main(String[] args) {
        
        // Pipeline conditions
        double tempC = 60.0;
        double pressure = 80.0;
        double co2MolFrac = 0.03;
        
        SystemElectrolyteCPAstatoil fluid = 
            new SystemElectrolyteCPAstatoil(tempC + 273.15, pressure);
        
        fluid.addComponent("methane", 0.85);
        fluid.addComponent("CO2", co2MolFrac);
        fluid.addComponent("water", 0.10);
        fluid.addComponent("Na+", 0.01);
        fluid.addComponent("Cl-", 0.01);
        fluid.addComponent("Fe++", 0.00002);
        fluid.addComponent("HCO3-", 0.002);
        
        fluid.chemicalReactionInit();
        fluid.createDatabase(true);
        fluid.setMixingRule(10);
        
        ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
        ops.TPflash();
        fluid.init(3);
        
        // Get CO2 partial pressure (fugacity in aqueous phase is more accurate)
        double pCO2 = fluid.getComponent("CO2").getx() * pressure;
        
        // de Waard-Milliams correlation (simplified)
        double tempK = tempC + 273.15;
        double logCR = 5.8 - 1710.0/tempK + 0.67 * Math.log10(pCO2);
        double corrosionRate = Math.pow(10, logCR);
        
        // pH correction factor (higher pH = lower corrosion)
        int aqPhase = fluid.getPhaseNumberOfPhase("aqueous");
        double pH = fluid.getPhase(aqPhase).getpH();
        
        // Simplified pH correction (actual models are more complex)
        double phFactor = 1.0;
        if (pH > 5.0) {
            phFactor = Math.pow(10, -(pH - 5.0) * 0.5);
        }
        
        double correctedCR = corrosionRate * phFactor;
        
        // FeCO3 protection factor
        ops.checkScalePotential(aqPhase);
        String[][] results = ops.getResultTable();
        double feco3SR = 0;
        for (int i = 1; i < results.length; i++) {
            if (results[i][0] != null && results[i][0].equals("FeCO3")) {
                feco3SR = Double.parseDouble(results[i][1]);
                break;
            }
        }
        
        // If FeCO3 layer forms, significant protection
        if (feco3SR > 1.0) {
            correctedCR *= 0.1;  // 90% reduction (typical for good siderite layer)
        }
        
        System.out.println("=== CORROSION ASSESSMENT ===");
        System.out.println();
        System.out.printf("Temperature: %.0f °C%n", tempC);
        System.out.printf("Pressure: %.0f bara%n", pressure);
        System.out.printf("CO2 partial pressure: %.2f bar%n", pCO2);
        System.out.printf("Aqueous pH: %.2f%n", pH);
        System.out.printf("FeCO3 saturation ratio: %.3f%n", feco3SR);
        System.out.println();
        System.out.printf("Bare steel corrosion rate: %.2f mm/year%n", corrosionRate);
        System.out.printf("pH-corrected rate: %.2f mm/year%n", corrosionRate * phFactor);
        System.out.printf("With FeCO3 protection: %.2f mm/year%n", correctedCR);
        
        // Classification
        System.out.println();
        if (correctedCR < 0.1) {
            System.out.println("Risk: LOW - Acceptable without inhibitor");
        } else if (correctedCR < 1.0) {
            System.out.println("Risk: MODERATE - Consider pH stabilization");
        } else if (correctedCR < 5.0) {
            System.out.println("Risk: HIGH - pH stabilization required");
        } else {
            System.out.println("Risk: SEVERE - CRA materials may be needed");
        }
    }
}

Available Ions in NeqSim

The Electrolyte CPA model supports these ionic species:

Cations

Ion Name Use Case
Na+ Sodium NaOH, NaCl brine
K+ Potassium Formation water
Ca++ Calcium CaCO3 scale, formation water
Mg++ Magnesium Formation water
Fe++ Ferrous iron Corrosion product, FeCO3
Ba++ Barium BaSO4 scale
Sr++ Strontium Formation water
H3O+ Hydronium pH calculations

Anions

Ion Name Use Case
Cl- Chloride Brine salinity
OH- Hydroxide NaOH treatment
HCO3- Bicarbonate CO2 system equilibrium
CO3-- Carbonate Scale, pH stabilization
SO4-- Sulfate Formation water, BaSO4 scale
S-- Sulfide H2S systems
Ac- Acetate Organic acids

Industry Best Practices

pH Stabilization Guidelines (NORSOK M-506)

Parameter Guideline
Target pH 6.0 - 6.5 for optimal FeCO3
NaOH purity > 99% recommended
Injection point Upstream of temperature drop
Monitoring Continuous pH measurement
Iron control 1-10 mg/L Fe++ optimal

Siderite Layer Quality Indicators

Indicator Good Layer Poor Layer
pH 6.0-6.5 < 5.5
Temperature > 60°C < 40°C
Flow velocity < 3 m/s > 5 m/s
Scaling tendency (SR) 1-10 < 1 or > 100
Fluid stability Continuous flow Slug flow

References

  1. de Waard, C. and Milliams, D.E. (1975). “Carbonic Acid Corrosion of Steel.” Corrosion, 31(5), 177-181.

  2. Dugstad, A. (2006). “Fundamental Aspects of CO2 Metal Loss Corrosion.” NACE Corrosion 2006, Paper 06111.

  3. Nyborg, R. (2010). “Guidelines for Prediction of CO2 Corrosion in Oil and Gas Production Systems.” IFE Report, IFE/KR/E-2010/003.

  4. NORSOK M-506 (2017). “CO2 Corrosion Rate Calculation Model.” Norwegian Technology Standards Institution.

  5. Nordsveen, M. et al. (2003). “A Mechanistic Model for Carbon Dioxide Corrosion of Mild Steel in the Presence of Protective Iron Carbonate Films.” Corrosion, 59(5), 443-456.

  6. Olsen, S. and Dugstad, A. (1991). “Corrosion under Dewing Conditions.” NACE Corrosion 91, Paper 91472.


See Also