agentleFS
equinorequinor/neqsim/.github/skills/neqsim-electrolyte-systems/SKILL.md

neqsim-electrolyte-systems

Skill167 starsChanged 7 months agoApache-2.0View on GitHub

Electrolyte and brine chemistry guidance for NeqSim. USE WHEN: modeling produced water, scale prediction, CO2/H2S in aqueous systems, MEG/DEG injection, hydrate inhibitor dosing, or any system with ions, salts, or electrolytes. Covers SystemElectrolyteCPAstatoil setup, ion components, scale risk assessment, and brine handling patterns.

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 3 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    When both K+ and Mg++ are present (including zero-amount components), the pinned catalog also lacks their theta row.

    line 41
  2. Very specific:

    Use applyPhreeqcCo2ChlorideParameters(zeta, potassiumMagnesiumTheta) with an explicit finite value.

    line 42
  3. Distinctive:

    Do not silently fill missing tuples or equate parameter coverage with scientific validation.

    line 46
  4. Very specific:

    Before hand-rolling the flash, note these ready-made helpers (see the neqsim-flow-assurance skill for full patterns):

    line 128
  5. Distinctive:

    Use the pure-mineral operation when the engineering question is gypsum/anhydrite equilibrium rather than screening SI:

    line 155
  6. Very specific:

    Multi-phase check: Always enable for electrolyte systems (setMultiPhaseCheck(true))

    line 360
  7. Distinctive:

    Missing counter-ions: Always add both cation and anion (e.g., Na+ with Cl-)

    line 363

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Electrolyte Systems Guide

Guide for modeling electrolyte/brine systems in NeqSim.

When to Use Electrolyte Models

  • Produced water with dissolved salts (NaCl, CaCl2, BaCl4)
  • Scale prediction (CaCO3, BaSO4, CaSO4)
  • CO2/H2S solubility in brines
  • MEG injection rate calculations
  • Hydrate inhibitor dosing
  • Seawater injection and mixing
  • Desalination process modeling

EOS Selection for Electrolyte Systems

SystemNeqSim ClassMixing Rule
Water + salt + HC gasSystemElectrolyteCPAstatoil10
MEG/DEG + water + gasSystemSrkCPAstatoil10
Pure water + gasSystemSrkCPAstatoil10
Brine + multiple saltsSystemElectrolyteCPAstatoil10

Basic Electrolyte Setup

Pitzer hydrate and mixed-chloride route

For Pitzer water activities use SystemPitzer with setMixingRule("classic") and setMultiPhaseCheck(true). The hydrate onset temperature/pressure/curve operations use PitzerHydrateFlash and a matched liquid-water reference. See docs/thermo/pitzer_hydrate_equilibrium.md for the tested setup and limits. applyPhreeqcCo2ChlorideParameters(Map<String, Double>) loads the pinned PHREEQC Na/K/Ca/Mg-Cl and CO2 lambda rows, but requires an explicit CO2-cation-Cl zeta for every present cation. Zero is an explicit screening assumption, not a fitted catalog coefficient. It remains unqualified. When both K+ and Mg++ are present (including zero-amount components), the pinned catalog also lacks their theta row. Use applyPhreeqcCo2ChlorideParameters(zeta, potassiumMagnesiumTheta) with an explicit finite value; the one-argument overload rejects that topology. For concentration sweeps retain the same components and neutral parameter dataset at zero salt. See the guide's 57-point reference assessment: numerical convergence and a passing regression suite do not mean every experimental comparison passed the 1 K accuracy criterion. Do not silently fill missing tuples or equate parameter coverage with scientific validation.

Hand off to neqsim-flow-assurance / flow.assurance with the configured fluid, salt mole basis, dataset identity, supplied zeta/theta values, pressure grid and operating temperature. Carry forward the Henry-domain lower limit (CO2 274.19 K; methane 275.46 K) and onset-only scope; subzero hydrate predictions require a qualified low-temperature aqueous gas reference.

import neqsim.thermo.system.SystemElectrolyteCPAstatoil;

// Create electrolyte system
SystemInterface brine = new SystemElectrolyteCPAstatoil(273.15 + 80.0, 200.0);

// Add gas components
brine.addComponent("CO2", 0.05);
brine.addComponent("methane", 0.80);

// Add water
brine.addComponent("water", 0.10);

// Add salt components (as ions)
brine.addComponent("Na+", 0.02);
brine.addComponent("Cl-", 0.02);
brine.addComponent("Ca++", 0.005);
brine.addComponent("SO4--", 0.005);

// Set mixing rule (MUST be numeric for CPA)
brine.setMixingRule(10);
brine.setMultiPhaseCheck(true);

Common Ion Components in NeqSim

IonNeqSim NameCommon Source
Sodium"Na+"NaCl
Chloride"Cl-"NaCl, CaCl2
Calcium"Ca++"CaCl2, CaCO3
Barium"Ba++"BaSO4, BaCl2
Strontium"Sr++"SrSO4
Sulfate"SO4--"Na2SO4, BaSO4
Bicarbonate"HCO3-"NaHCO3
Carbonate"CO3--"CaCO3, Na2CO3
Magnesium"Mg++"MgCl2
Potassium"K+"KCl
Iron(II)"Fe++"FeCl2, FeS

Produced Water Modeling

// Typical produced water composition
SystemInterface prodWater = new SystemElectrolyteCPAstatoil(273.15 + 80.0, 50.0);

// Dissolved gas
prodWater.addComponent("CO2", 0.01);
prodWater.addComponent("H2S", 0.001);
prodWater.addComponent("methane", 0.005);

// Water (dominant)
prodWater.addComponent("water", 0.90);

// Ions (typical North Sea produced water)
prodWater.addComponent("Na+", 0.03);
prodWater.addComponent("Cl-", 0.035);
prodWater.addComponent("Ca++", 0.003);
prodWater.addComponent("Ba++", 0.0001);
prodWater.addComponent("SO4--", 0.001);
prodWater.addComponent("HCO3-", 0.005);

prodWater.setMixingRule(10);
prodWater.setMultiPhaseCheck(true);

// Flash to get gas/liquid split
ThermodynamicOperations ops = new ThermodynamicOperations(prodWater);
ops.TPflash();
prodWater.initProperties();

Scale Risk Assessment

Higher-level scale helpers (prefer for screening)

Before hand-rolling the flash, note these ready-made helpers (see the neqsim-flow-assurance skill for full patterns):

  • ElectrolyteScaleCalculator — activity-corrected SI for CaCO3, BaSO4, CaSO4, SrSO4 from ion mg/L (Davies + Ksp(T)).
  • ScaleKinetics — induction time and reaction-vs-transport growth regime on top of an SI (SI says if, kinetics says how fast).
  • BrineMixingScaleEvaluator — seawater/formation-water mixing sweep; finds the worst mixing fraction and mineral (sulphate scale often peaks mid-mix).
  • ScaleDepositionAccumulator / PipeSegmentIntegrity — deposition and coupled corrosion+scale along a PipeBeggsAndBrills profile.
  • Rigorous scale potential (saturation ratio SR = IAP/Ksp per salt): ops.checkScalePotential(phaseNumber) then ops.getResultTable(). This uses in-situ ion molalities, so it needs chemical reactions + speciation — call system.chemicalReactionInit() before createDatabase(true)/setMixingRule(10) and flash first (otherwise carbonate/bicarbonate/pH speciation is missing and the SR is wrong).

Once the SI/SR is known, the inhibitor side (minimum inhibitor concentration, residual SI after treatment, chemical compatibility, and what a pH adjuster or H2S scavenger does to the brine) lives in neqsim-production-chemistry (ScaleInhibitorPerformance, ScaleControlAssessor, ProductionChemicalScaleScenario). A StreamChemistryAdapter / fromStream(...) helper reads the ion table straight off the electrolyte stream built here — do not retype it.

Activity-consistent calcium-sulfate equilibrium

Use the pure-mineral operation when the engineering question is gypsum/anhydrite equilibrium rather than screening SI:

MultiSaltPrecipitationResult solids =
    new ThermodynamicOperations(brine).precipitateScales("CaSO4_A", "CaSO4_G");

CalciumSulfatePhaseBoundaryQualification evidence =
    new ThermodynamicOperations(brine).qualifyCalciumSulfatePhaseBoundary();

CaSO4_G is hydrated gypsum: the authoritative operation includes $a_{\mathrm{w}}^2$, removes or returns two water moles per formula unit, and reports hydrated mass. CaSO4_A remains water-free anhydrite. The qualification object separates this mineral-standard-state evidence from the Pitzer or electrolyte-CPA aqueous activity model. It is currently fail-closed against the CC BY 4.0 Voigt–Freyer pure-water and NaCl crossing envelopes and does not qualify high-pressure use; do not fit or reinterpret Pitzer interactions to hide a mineral-correlation mismatch. See docs/pvtsimulation/scale_prediction_api.md for the source matrix, numerical residuals, and limits. The object registers the NIST ThermoML CaCl2 pressure series as independent finite-concentration evidence, but explicitly leaves the dilute limiting-volume term unresolved; do not treat the presence of pressure evidence as high-pressure calcium-sulfate qualification.

CaCO3 (Calcite) Scaling

Scale forms when the product of ion activities exceeds the solubility product:

$$ SI = \log_{10}\left(\frac{a_{Ca^{2+}} \cdot a_{CO_3^{2-}}}{K_{sp}}\right) $$

Where $SI > 0$ indicates supersaturation (scaling risk).

// Rigorous route: mix formation water + seawater, solve speciation, check scale
SystemInterface mixed = new SystemElectrolyteCPAstatoil(273.15 + 80.0, 50.0);
// Add components from both waters (water, Na+, Cl-, Ca++, Ba++, SO4--, HCO3-, CO2 ...)
mixed.chemicalReactionInit();   // MANDATORY for pH + carbonate speciation + SR
mixed.createDatabase(true);
mixed.setMixingRule(10);
mixed.setMultiPhaseCheck(true);

ThermodynamicOperations ops = new ThermodynamicOperations(mixed);
ops.TPflash();                  // solves aqueous speciation
mixed.initProperties();

// (1) Scale potential per salt: saturation ratio SR = IAP/Ksp (>1 => scaling)
int aq = mixed.getPhaseNumberOfPhase("aqueous");
ops.checkScalePotential(aq);
String[][] sr = ops.getResultTable();     // rows: {saltName, SR, ""}
double pH = mixed.getpH();                // speciated in-situ pH from same flash

// (2) Precipitation amount: a supersaturated brine drops a solid phase
if (mixed.hasPhaseType("solid")) {
    double solidMoles = mixed.getPhase("solid").getNumberOfMolesInPhase();
    double solidMassKg = solidMoles * mixed.getPhase("solid").getMolarMass();
}

BaSO4 (Barite) Scaling

Most problematic scale in oil production — forms when Ba++ meets SO4--.

// Formation water (high Ba++)
SystemInterface formWater = new SystemElectrolyteCPAstatoil(273.15 + 90.0, 200.0);
formWater.addComponent("water", 0.95);
formWater.addComponent("Na+", 0.025);
formWater.addComponent("Cl-", 0.020);
formWater.addComponent("Ba++", 0.001);  // High barium
formWater.addComponent("Sr++", 0.0005);
formWater.setMixingRule(10);

// Injection water (high SO4--)
SystemInterface injWater = new SystemElectrolyteCPAstatoil(273.15 + 20.0, 200.0);
injWater.addComponent("water", 0.96);
injWater.addComponent("Na+", 0.015);
injWater.addComponent("Cl-", 0.015);
injWater.addComponent("SO4--", 0.01);   // High sulfate
injWater.setMixingRule(10);

Cathodic (high-pH) scaling in seawater electrochemical devices

Electrochlorination cells, seawater electrolysers and impressed-current cathodic protection all evolve hydrogen at the cathode, which lifts the boundary-layer pH far above the bulk seawater value of ≈ 8.1. The deposit there is brucite Mg(OH)₂ together with CaCO₃, not the sulphate minerals produced-water work usually screens for. Set the boundary-layer pH, not the bulk pH:

ElectrolyteScaleCalculator calc = new ElectrolyteScaleCalculator()
    .setTemperatureCelsius(8.0).setPressureBara(1.013)
    .setPH(10.5)                       // cathode boundary layer, NOT the bulk 8.1
    .setCations(412.0, 0.02, 7.9, 1290.0, 10780.0, 399.0, 0.002)  // S = 35 seawater, mg/L
    .setAnions(19350.0, 2710.0, 142.0, 0.0);
calc.calculate();
double siBrucite = calc.getBruciteSaturationIndex();   // Mg(OH)2 - two decades per pH unit
double siCalcite = calc.getCaCO3SaturationIndex();
double aOH = calc.getHydroxideActivity();

Bulk S = 35 seawater at 8 °C is already supersaturated in calcite (SI ≈ +0.58, Ω ≈ 3.8) but strongly undersaturated in brucite (SI ≈ −4.8); brucite crosses SI = 0 near pH 10.5 at 8 °C and near pH 10.1 at 14 °C (retrograde solubility, so it is worse in warm water).

getpH() on a loaded SystemElectrolyteCPAstatoil can return a flat 7.0 without an explicit reaction/ion setup — for boundary-layer work set the pH explicitly on the scale calculator instead of reading it back from a flash.

Cell voltage is an electrolyte-transport question. For a current-controlled device the applied voltage floats with seawater resistivity, so a fixed voltage limit (vendor maximum, Ex/ATEX certification) has a temperature-dependent margin. Use neqsim.process.chemistry.electrochlorination.SeawaterElectrolyteConductivity (UNESCO/PSS-78) for conductivity, resistivity and the ohmic voltage:

SeawaterElectrolyteConductivity sw = new SeawaterElectrolyteConductivity()
    .setSalinityPsu(35.0).setTemperatureCelsius(8.0);
sw.calculate();
double kappa = sw.getConductivitySPerM();                       // ≈ 3.62 S/m
double ratio = sw.ohmicVoltageRatioVersusTemperature(15.0);     // ≈ 1.19 vs a 15 °C design basis

Chain: this skill → neqsim-flow-assurance (scale kinetics, remediation) for deposition rate and dissolver selection; → neqsim-standards-lookup for NORSOK S-001 / ISO 13702 when the consequence is firewater or seawater-system availability.

MEG Injection Calculations

// Wet gas with MEG injection for hydrate prevention
SystemInterface wetGas = new SystemSrkCPAstatoil(273.15 + 5.0, 100.0);
wetGas.addComponent("methane", 0.85);
wetGas.addComponent("ethane", 0.08);
wetGas.addComponent("propane", 0.03);
wetGas.addComponent("water", 0.03);
wetGas.addComponent("MEG", 0.01);  // Monoethylene glycol
wetGas.setMixingRule(10);
wetGas.setMultiPhaseCheck(true);

// Check hydrate temperature with MEG
ThermodynamicOperations ops = new ThermodynamicOperations(wetGas);
ops.hydrateFormationTemperature();
double hydrateT = wetGas.getTemperature() - 273.15;  // °C

// Compare with and without MEG to get suppression

CO2 Solubility in Brine

Important for CCS and EOR projects:

// CO2 solubility decreases with salinity (salting-out effect)
SystemInterface co2Brine = new SystemElectrolyteCPAstatoil(273.15 + 50.0, 100.0);
co2Brine.addComponent("CO2", 0.10);
co2Brine.addComponent("water", 0.80);
co2Brine.addComponent("Na+", 0.05);
co2Brine.addComponent("Cl-", 0.05);
co2Brine.setMixingRule(10);

ThermodynamicOperations ops = new ThermodynamicOperations(co2Brine);
ops.TPflash();
co2Brine.initProperties();

// CO2 in aqueous phase
double co2InWater = co2Brine.getPhase("aqueous").getComponent("CO2").getx();

// In-situ pH — getpH() has a built-in acid-gas fallback, so a CO2/H2S brine
// returns an acidic pH even WITHOUT chemicalReactionInit() (no more silent 7.0).
double pH = co2Brine.getpH();               // ~3.9 for CO2-saturated water
// double pH = co2Brine.getPhase("aqueous").getpH("acidgas"); // force the estimate

For a rigorous speciated pH in a buffered brine (explicit H3O+/HCO3-/OH-), run system.chemicalReactionInit() before createDatabase(true) / setMixingRule(10). The getpH("acidgas") fallback is a screening estimate that ignores bicarbonate buffering and salt-ion alkalinity — see the electrolyte-CPA model docs and the flow-assurance skill for corrosion use.

Key Units and Conversions

QuantityUnitConversion
Ion concentrationmol fractionppm = x × MW_solution / MW_ion × 1e6
TDSmg/LSum of all dissolved ion concentrations
Salinitywt% NaCl equivBased on Na+/Cl- content
pHdimensionlessFrom H+ activity

Gas-solubility reference assessments

Match reported gas partial pressure and final aqueous salt molality. Account for solvent evaporation: 1 kg feed water need not leave 1 kg aqueous water. Rebuild independent trial fluids, compensate feed water, and bracket from valid liquid-bearing states. Verify component inventory, ion confinement and molecular fugacity equality before using a trial in a pressure root. Check that changing the finite gas-reservoir size leaves the matched result unchanged. Use init(1) for fixed-T/P fugacities, retaining accuracy deviations as evidence. See KClCO2SolubilityReferenceAssessmentTest and the benchmark notes.

Common Pitfalls

  1. Charge balance: Total positive charges must equal total negative charges
  2. Mixing rule is model-specific: CPA requires numeric 10; SystemPitzer uses "classic"
  3. Ion names are case-sensitive: "Na+" not "na+" or "NA+"
  4. Multi-phase check: Always enable for electrolyte systems (setMultiPhaseCheck(true))
  5. Temperature limits: Electrolyte models may have narrower valid T range than HC models
  6. Convergence: Electrolyte flashes can be slow — be patient or reduce component count
  7. Missing counter-ions: Always add both cation and anion (e.g., Na+ with Cl-)

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.