neqsim-production-chemistry · git:20260907.9ca9ea2 · 2026-09-07 · sha256 52d088f7f6d23d10
neqsim-production-chemistry git:20260907.9ca9ea2A
Immutable. This exact content is served forever at /api/v1/blob/52d088f7f6d23d10.
---
name: neqsim-production-chemistry
description: "Production-chemistry patterns for NeqSim. USE WHEN: selecting or dosing production chemicals (scale inhibitor, corrosion inhibitor, MEG/MeOH THI, KHI/AA LDHI, wax inhibitor, asphaltene inhibitor, H2S scavenger, oxygen scavenger, biocide, demulsifier, antifoam, pH adjuster, chelant, acid), checking chemical-chemical or chemical-fluid COMPATIBILITY of an injection cocktail, computing minimum effective dose / MIC / residual saturation index / inhibited corrosion rate / scavenger breakthrough, placing a chemical injection point in a flowsheet, optimising demulsifier dose against an oil-in-water spec, quantifying how a treatment changes brine pH and scaling tendency, or running an explainable chemical root-cause analysis on a deposit, emulsion, pH excursion or H2S breakthrough. Anchors on neqsim.process.chemistry and neqsim.process.equipment.watertreatment."
last_verified: "2026-09-07"
---
# Production Chemistry with NeqSim
`neqsim.process.chemistry` is an open, standards-traceable production-chemistry stack:
chemical inventory, compatibility rules, per-threat dose-response models, a
flowsheet-visible injection point, and an explainable root-cause analyser. Every model
exposes `getStandardsApplied()` and `toMap()` / `toJson()` so results drop straight into
`results.json`.
## When to Use This Skill
- Which chemical, which chemistry family, and **what dose** for a given threat
- **Compatibility** of a chemical cocktail with itself, with the produced water, and with the material
- Minimum inhibitor concentration (MIC), residual SI after treatment, inhibited corrosion rate
- H2S scavenger demand, capacity and breakthrough time
- Hydrate inhibitor (MEG/MeOH) injection rate for a target subcooling; KHI induction time
- Demulsifier dose vs oil-in-water spec, dosing lag, monthly compliance
- How a pH adjuster / scavenger shifts the brine chemistry and the scaling tendency
- Chemical incident root cause (deposit, emulsion, corrosion, pH excursion, H2S breakthrough)
- Uncertainty (P10/P50/P90) on any of the above
## What This Skill Is NOT
- **Thermodynamics of the threat** — hydrate curve, WAT, asphaltene onset, SI from an ion
table: load `neqsim-flow-assurance`, `neqsim-wax-calculations`, `neqsim-electrolyte-systems`.
This skill consumes those results and answers *what chemical, how much, and what happens*.
- **Rigorous reaction kinetics** — load `neqsim-reaction-engineering`.
- **Equipment root cause from historian data** — load `neqsim-root-cause-analysis`.
`RootCauseAnalyser` here is the *chemical* RCA (deposit / chemistry symptoms).
## Applicable Standards
| Domain | Standards |
|--------|-----------|
| Scale prediction and inhibitor testing | NACE TM0374, NORSOK M-001 |
| Corrosion inhibitor selection and monitoring | NACE SP0775, NORSOK M-506, ISO 21457 |
| Sour service / H2S | NACE MR0175 / ISO 15156, NACE TM0169, GPSA §21 |
| Produced water discharge | OSPAR 2001/1 (30 mg/L OiW monthly average), NORSOK S-002 |
| Chemical selection / HSE | OSPAR HOCNF, PLONOR (screening context only — NeqSim does not score ecotoxicity) |
Load `neqsim-standards-lookup` and emit `standards_applied` in `results.json`.
## 1. Chemical Inventory — `ProductionChemical`
Every chemical is described once and reused by the compatibility, scenario and RCA models.
```java
import neqsim.process.chemistry.ProductionChemical;
// Factory helpers set typical defaults for the family
ProductionChemical si = ProductionChemical.scaleInhibitor("SI-A", 20.0); // 20 ppm
ProductionChemical ci = ProductionChemical.corrosionInhibitor("CI-B", 50.0); // 50 ppm
ProductionChemical meg = ProductionChemical.thermodynamicHydrateInhibitor("MEG", 0.0);
ProductionChemical scav = ProductionChemical.h2sScavenger("Triazine", 100.0);
// Tune before evaluation
si.setActiveIngredient("phosphonate");
si.setActiveWtPct(35.0);
si.setIonicNature(ProductionChemical.IonicNature.ANIONIC);
si.setTemperatureRangeC(4.0, 150.0);
si.setPH(3.5);
boolean ok = si.isStableAt(120.0);
```
`ChemicalType`: `SCALE_INHIBITOR`, `CORROSION_INHIBITOR`, `HYDRATE_INHIBITOR_THERMODYNAMIC`,
`HYDRATE_INHIBITOR_LDHI`, `DEMULSIFIER`, `WAX_INHIBITOR`, `ASPHALTENE_INHIBITOR`, `BIOCIDE`,
`OXYGEN_SCAVENGER`, `H2S_SCAVENGER`, `ANTIFOAM`, `DRAG_REDUCER`, `PH_ADJUSTER`, `ACID`,
`CHELANT`, `OTHER`.
`IonicNature`: `CATIONIC`, `ANIONIC`, `NON_IONIC`, `AMPHOTERIC`, `UNKNOWN` — this is what
drives most incompatibility findings (anionic SI + cationic CI → precipitation).
## 2. Compatibility — `ChemicalCompatibilityAssessor`
Rule base is CSV-driven (`/data/chemical_compatibility_rules.csv`, loaded by
`ChemicalInteractionRule.loadDefaultRules()`), combined with the operating conditions and
water chemistry you supply.
```java
import neqsim.process.chemistry.ChemicalCompatibilityAssessor;
ChemicalCompatibilityAssessor assessor = new ChemicalCompatibilityAssessor();
assessor.addChemical(si);
assessor.addChemical(ci);
assessor.addChemical(scav);
assessor.setTemperatureCelsius(85.0);
assessor.setPressureBara(90.0);
assessor.setCalciumMgL(1200.0);
assessor.setIronMgL(5.0);
assessor.setBicarbonateMgL(300.0);
assessor.setMaterial("carbon steel");
assessor.evaluate();
ChemicalCompatibilityAssessor.Verdict verdict = assessor.getVerdict(); // COMPATIBLE / CAUTION / INCOMPATIBLE
List<Map<String, Object>> issues = assessor.getIssues(); // mechanism + mitigation per issue
Map<String, Map<String, String>> matrix = assessor.getInteractionMatrix();
Map<String, Boolean> thermal = assessor.getThermalStability(); // per chemical at T
String json = assessor.toJson();
```
**Always run this before recommending a dose.** A dose that is right in isolation can be
destroyed by the neighbouring injection point.
`ChemicalCompatibilityAssessor.fromStream(stream)` pre-fills T, P and the water chemistry
from a live NeqSim stream — see §8.
## 3. Dose-Response per Threat
All performance models follow the same pattern: setters → `evaluate()` → getters +
`getWarnings()` + `getStandardsApplied()` + `toMap()`.
### 3.1 Scale inhibitor — `ScaleInhibitorPerformance`
```java
import neqsim.process.chemistry.scale.ScaleInhibitorPerformance;
ScaleInhibitorPerformance sip = new ScaleInhibitorPerformance();
sip.setScaleType(ScaleInhibitorPerformance.ScaleType.BASO4); // CACO3 BASO4 SRSO4 CASO4 FECO3
sip.setInhibitorChemistry(ScaleInhibitorPerformance.InhibitorChemistry.PHOSPHONATE);
sip.setTemperatureCelsius(95.0);
sip.setSaturationRatio(12.0); // from the SI/SR calculation (see neqsim-flow-assurance)
sip.setTdsMgL(90000.0);
sip.setCalciumMgL(1200.0);
sip.setAvailableDoseMgL(15.0); // what the squeeze/umbilical actually delivers
sip.evaluate();
double mic = sip.getMinimumInhibitorConcentrationMgL();
double recommended = sip.getRecommendedDoseMgL();
double eff = sip.getEfficiency();
boolean adequate = sip.isAdequate(); // available dose >= MIC
```
`InhibitorChemistry`: `PHOSPHONATE`, `POLYMALEATE`, `POLYACRYLATE`, `PHOSPHATE_ESTER`,
`VINYL_SULPHONATE`.
### 3.2 Whole-brine scale control — `ScaleControlAssessor`
Couples a `ScalePredictionCalculator` with one inhibitor model per mineral and reports the
**residual** SI and a kinetic risk index.
```java
import neqsim.pvtsimulation.flowassurance.ScalePredictionCalculator;
import neqsim.process.chemistry.scale.ScaleControlAssessor;
ScalePredictionCalculator pred = new ScalePredictionCalculator();
pred.setTemperatureCelsius(95.0);
pred.setPressureBara(150.0);
pred.setCalciumConcentration(1200.0);
pred.setBariumConcentration(250.0);
pred.setSulphateConcentration(1800.0);
pred.setBicarbonateConcentration(300.0);
pred.setTotalDissolvedSolids(90000.0);
pred.setPH(6.2);
ScaleControlAssessor control = new ScaleControlAssessor(pred);
control.addInhibitor(ScaleInhibitorPerformance.ScaleType.BASO4, sip);
control.evaluate();
double residual = control.getResidualSI(ScaleInhibitorPerformance.ScaleType.BASO4);
double worst = control.getWorstResidualSI();
boolean controlled = control.isControlled(0.0); // residual SI <= threshold
boolean kinetic = control.isKineticallyControlled(1.0);
```
### 3.3 Corrosion inhibitor — `CorrosionInhibitorPerformance`
Langmuir adsorption with a van 't Hoff temperature term, shear-stripping, and penalties for
O2, organic acid and H2S.
```java
import neqsim.process.chemistry.corrosion.CorrosionInhibitorPerformance;
CorrosionInhibitorPerformance cip = new CorrosionInhibitorPerformance();
cip.setChemistry(CorrosionInhibitorPerformance.InhibitorChemistry.IMIDAZOLINE);
cip.setDoseMgL(50.0);
cip.setBaseCorrosionRateMmYr(2.4); // from NORSOK M-506 / de Waard-Milliams
cip.setTemperatureCelsius(70.0);
cip.setWallShearStressPa(35.0);
cip.setOrganicAcidPpm(200.0);
cip.setH2SPartialPressureBar(0.02);
cip.setOxygenPpb(10.0);
cip.evaluate();
double efficiency = cip.getEfficiency(); // 0..1
double inhibited = cip.getInhibitedCorrosionRateMmYr();
double minDose = cip.getMinimumEffectiveDoseMgL();
Map<String, String> warn = cip.getWarnings(); // e.g. O2 > 50 ppb kills film
```
`InhibitorChemistry`: `IMIDAZOLINE`, `QUATERNARY_AMMONIUM`, `AMIDO_AMINE`, `PHOSPHATE_ESTER`,
`PYRIDINE`, `MERCAPTAN`. `setFromDeWaardMilliams(baseline)` takes the uninhibited rate
directly from a `DeWaardMilliamsCorrosion` object.
> **O2 ingress dominates.** Above ~50 ppb the model warns that film-forming CI is undermined —
> the answer is an oxygen scavenger or eliminating the ingress, not more CI.
### 3.4 Thermodynamic hydrate inhibitor (MEG / MeOH) — `ThermodynamicHydrateInhibitorPerformance`
```java
import neqsim.process.chemistry.hydrate.ThermodynamicHydrateInhibitorPerformance;
ThermodynamicHydrateInhibitorPerformance thi =
new ThermodynamicHydrateInhibitorPerformance();
thi.setInhibitorChemistry(ThermodynamicHydrateInhibitorPerformance.InhibitorChemistry.MEG);
thi.setTargetSubcoolingC(8.0); // hydrate T minus minimum operating T
thi.setWaterFlowKgPerHour(1500.0); // free + condensed water
thi.setInhibitorPurityWtPct(90.0); // rich/lean MEG purity
thi.setLeanInhibitorWtPctInWater(0.0); // already present in the water
thi.evaluate();
double wtPct = thi.getRequiredInhibitorWtPctInWater();
double kgHr = thi.getRequiredInjectionKgPerHour();
```
`InhibitorChemistry`: `METHANOL`, `MEG`, `DEG`, `TEG` (Hammerschmidt K built in).
> Hammerschmidt is a screening correlation. For the design number, compute the inhibited
> hydrate curve with `SystemSrkCPAstatoil` + `hydrateFormationTemperature()`
> (`neqsim-flow-assurance`) and use this model for the injection-rate bookkeeping and
> lean/rich balance.
### 3.5 Kinetic hydrate inhibitor (KHI) — `KineticHydrateInhibitorPerformance`
```java
import neqsim.process.chemistry.hydrate.KineticHydrateInhibitorPerformance;
KineticHydrateInhibitorPerformance khi = new KineticHydrateInhibitorPerformance();
khi.setSubcoolingC(6.0);
khi.setDoseWtPct(0.5);
khi.setTargetInductionTimeHours(24.0);
khi.evaluate();
double tInd = khi.getPredictedInductionTimeHours();
double required = khi.getRequiredDoseWtPct();
```
Calibrate with `setCoefficients(a, b, c)` against vendor rocking-cell data before quoting.
### 3.6 Wax and asphaltene inhibitors
```java
import neqsim.process.chemistry.wax.WaxInhibitorPerformance;
import neqsim.process.chemistry.asphaltene.AsphalteneInhibitorPerformance;
WaxInhibitorPerformance wax = new WaxInhibitorPerformance();
wax.setInhibitorChemistry(WaxInhibitorPerformance.InhibitorChemistry.EVA);
wax.setBasePourPointC(24.0);
wax.setBaseWaxAppearanceTemperatureC(32.0); // untreated WAT from neqsim-wax-calculations
wax.setDoseMgL(300.0);
wax.evaluate();
double ppd = wax.getPourPointDepressionC();
double treatedWat = wax.getInhibitedWaxAppearanceTemperatureC();
AsphalteneInhibitorPerformance asp = new AsphalteneInhibitorPerformance();
asp.setInhibitorChemistry(AsphalteneInhibitorPerformance.InhibitorChemistry.ALKYLPHENOL_RESIN);
asp.setBaseColloidalInstabilityIndex(1.2);
asp.setBaseAsphalteneOnsetPressureBara(280.0);
asp.setDoseMgL(150.0);
asp.evaluate();
boolean stable = asp.isStableAfterTreatment();
```
A wax inhibitor depresses the **pour point** far more than the WAT — do not sell a WAT shift
the model does not give you.
### 3.7 H2S scavenger — `H2SScavengerPerformance`
```java
import neqsim.process.chemistry.scavenger.H2SScavengerPerformance;
H2SScavengerPerformance scv = new H2SScavengerPerformance();
scv.setChemistry(H2SScavengerPerformance.ScavengerChemistry.MEA_TRIAZINE);
scv.setActiveWtPct(40.0);
scv.setScavengerInventoryKg(20000.0);
scv.setGasFlowMSm3PerDay(4.0);
scv.setH2SInletPpm(35.0);
scv.setH2STargetPpm(4.0); // sales-gas spec
scv.setTemperatureCelsius(40.0);
scv.setPressureBara(70.0);
scv.evaluate();
double removeKgD = scv.getH2SToRemoveKgPerDay();
double demandKgD = scv.getScavengerDemandKgPerDay();
double capacity = scv.getCapacityKgH2SPerKgActive();
double breakthroughDays = scv.getBreakthroughDays();
```
`ScavengerChemistry`: `MEA_TRIAZINE`, `MMA_TRIAZINE`, `IRON_CHELATE`, `IRON_SPONGE`,
`ALDEHYDE`. For a **solid packed bed** with an axial profile use
`neqsim.process.chemistry.scavenger.PackedBedScavengerReactor` (1D plug flow, per-cell
inventory depletion, breakthrough time). For a **gas contactor unit operation inside a
flowsheet**, use `neqsim.process.equipment.absorber.H2SScavenger`.
> Triazine overdose forms amorphous dithiazine deposits. If the symptom is a soft, sticky,
> sulphur-smelling deposit downstream of a scavenger skid, check
> `ScaleRemediationAdvisor` (it carries the amorphous scavenger-deposit case) and feed the
> symptom to `RootCauseAnalyser` — it has an explicit
> `H2S_SCAVENGER_UNDER_CAPACITY` / overdose candidate.
## 4. Injection Point as Flowsheet Equipment — `InhibitorInjectionPoint`
Makes chemical injection visible, snapshot-able and adjustable in a `ProcessSystem`.
```java
import neqsim.process.chemistry.equipment.InhibitorInjectionPoint;
InhibitorInjectionPoint inj = new InhibitorInjectionPoint("CI injection", wellStream);
inj.setChemical(ci);
inj.setDoseInPpmOnWater(50.0); // or setDoseInKgPerHour(...)
process.add(inj);
process.run();
double ppmInWater = inj.getActiveIngredientPpmInWater();
double kgPerHour = inj.getInjectionRateKgPerHour();
```
`DoseMode`: `PPM` (on water mass), `PPM_TOTAL` (on total fluid mass), `KG_PER_HOUR`.
> It deliberately does **not** run an electrolyte flash on every call — it tracks the dose at
> the outlet and hands it to the dedicated chemistry models. Do not expect it to change the
> hydrate curve or pH by itself.
## 5. Produced Water — Demulsifier Dose vs Oil-in-Water
```java
import neqsim.process.equipment.watertreatment.DemulsifierDoseResponseModel;
import neqsim.process.equipment.watertreatment.OilInWaterDoseOptimizer;
DemulsifierDoseResponseModel dr = new DemulsifierDoseResponseModel();
double rms = dr.calibrate(dosePpmArray, observedOiwArray, 250.0); // untreated OiW mg/L
double oiw = dr.predictOilInWater(250.0, 12.0); // effective dose ppm
OilInWaterDoseOptimizer opt = new OilInWaterDoseOptimizer();
opt.setDoseResponseModel(dr);
opt.setDoseRange(2.0, 40.0, 0.5);
opt.setSafetyMarginMgL(3.0);
OilInWaterDoseOptimizer.DoseRecommendation rec = opt.recommendDose(250.0, 900.0, 18);
double setpoint = rec.getSetpointDosePpm();
boolean feasible = rec.isFeasible();
```
The optimiser accounts for **dosing lag** (`ChemicalDoseLagModel` — first-order chemical
inventory after a setpoint change), analyser drift, and the OSPAR 30 mg/L *monthly average*
via `addMonthlySample(oiwMgL, volumeM3)` — so a mid-month excursion is traded against the
running average rather than the instantaneous reading. Overdosing is modelled (the response
curve turns over past the optimum): more demulsifier is not monotonically better.
Emulsion viscosity for the hydraulics side: `EmulsionViscosityCalculator`
(`neqsim.pvtsimulation.flowassurance`) has a `demulsifierPresent` / efficiency correction.
## 6. Treatment Effect on Brine Chemistry — `ProductionChemicalScaleScenario`
Answers "if I inject this pH stabiliser / scavenger, what happens to the scaling tendency?"
It converts chemical alkalinity to a closed carbonate balance, consumes dissolved sulphide by
scavenger capacity, and reports SI **before and after**.
```java
import neqsim.process.chemistry.scale.ProductionChemicalScaleScenario;
ProductionChemicalScaleScenario sc = new ProductionChemicalScaleScenario();
sc.addChemical(ProductionChemical.causticPHAdjuster("NaOH", 500.0))
.setTemperatureCelsius(80.0)
.setPressureBara(60.0)
.setPH(5.8)
.setCalciumMgL(1200.0)
.setBicarbonateMgL(300.0)
.setSulphateMgL(1800.0)
.setBariumMgL(250.0)
.setTotalDissolvedSolidsMgL(90000.0)
.setCO2PartialPressureBar(1.5);
sc.evaluate();
double treatedPH = sc.getTreatedPH();
double before = sc.getBaselineSaturationIndex("CaCO3");
double after = sc.getTreatedSaturationIndex("CaCO3");
double delta = sc.getSaturationIndexChange("CaCO3");
```
Supported pH actives: NaOH, soda ash, MDEA, MEA. This is the model that catches the classic
own-goal: a pH stabiliser injected for corrosion control pushes CaCO3 into scaling.
## 7. Chemical Root Cause — `RootCauseAnalyser`
Rule-based and **explainable** — every candidate carries an evidence narrative and a score,
tagged PRIMARY / CONTRIBUTING / POSSIBLE / RULED_OUT.
```java
import neqsim.process.chemistry.rca.RootCauseAnalyser;
import neqsim.process.chemistry.rca.Symptom;
RootCauseAnalyser rca = new RootCauseAnalyser();
rca.addSymptom(new Symptom(Symptom.Category.DEPOSIT, "hard white deposit in choke")
.withMeasurement("deposit_thickness_mm", 3.0)
.withConfidence(0.9));
rca.addSymptom(new Symptom(Symptom.Category.FLOW_RESTRICTION, "choke Cv down 25%"));
rca.addChemical(si);
rca.addChemical(ci);
rca.setCompatibilityAssessor(assessor);
rca.setChemicalTreatmentScenario(sc);
rca.setTemperatureCelsius(85.0);
rca.setPH(5.8);
rca.setCalciumMgL(1200.0);
rca.setBariumMgL(250.0);
rca.setSulphateMgL(1800.0);
rca.setOxygenPpb(15.0);
rca.setMaterial("carbon steel");
rca.analyse();
RootCauseCandidate primary = rca.getPrimary();
List<RootCauseCandidate> ranked = rca.getCandidates();
List<String> gaps = rca.getDataGaps(); // put these in results.json assumptions/gaps
```
`Symptom.Category`: `DEPOSIT`, `CORROSION`, `EMULSION`, `PH_EXCURSION`, `FLOW_RESTRICTION`,
`H2S_BREAKTHROUGH`, `SAMPLE_APPEARANCE`, `OFF_SPEC`, `OTHER`.
Add measurement evidence to sharpen the ranking:
```java
Map<String, Double> likelihoods = new LinkedHashMap<String, Double>();
likelihoods.put("BASO4_SCALE", 0.8);
likelihoods.put("CACO3_SCALE", 0.2);
rca.addEvidence(likelihoods);
Map<String, Double> posteriors = rca.getBayesianPosteriors();
```
Then close the loop with the remediation side:
```java
ScaleRemediationAdvisor advisor = new ScaleRemediationAdvisor();
List<ScaleRemediationAdvisor.RemediationOption> options = advisor.recommendFor("BaSO4");
```
`ScaleRemediationAdvisor` covers CaCO3, FeCO3, FeS, BaSO4, SrSO4, CaSO4, NaCl and the
amorphous H2S-scavenger deposit, with dissolver, concentration, method, temperature window,
cautions and a standard reference per option. Details in `neqsim-flow-assurance`.
## 8. Getting Inputs from a Live Stream — `StreamChemistryAdapter`
Do not hand-transcribe the water analysis. Pull it from the flowsheet:
```java
import neqsim.process.chemistry.util.StreamChemistryAdapter;
StreamChemistryAdapter ad = new StreamChemistryAdapter(stream);
double tC = ad.getTemperatureCelsius();
double pCO2 = ad.getPartialPressureBara("CO2");
double ca = ad.getCalciumMgL();
double tds = ad.getTdsMgL();
double h2sPpm = ad.getH2SInGasPpm();
double gasSm3d = ad.getGasFlowSm3PerDay();
double tau = ad.estimateWallShearStressPa(0.2, 4.0); // pipe ID m, velocity m/s
```
Convenience constructors that do this for you:
| Call | Fills |
|------|-------|
| `ChemicalCompatibilityAssessor.fromStream(stream)` | T, P, Ca, Fe, HCO3 |
| `CorrosionInhibitorPerformance.fromStream(stream, pipeIdM, velocityMps)` | T, shear, pH2S, base rate inputs |
| `ScaleControlAssessor.fromStream(stream)` | full ion table into the predictor |
| `RootCauseAnalyser.setWaterChemistryFromStream(stream)` | full ion table + partial pressures |
The stream must carry an aqueous phase with the ions — build it with
`SystemElectrolyteCPAstatoil` (`neqsim-electrolyte-systems`). A dry-gas stream returns zeros;
check before trusting a "no scale risk" verdict.
## 9. Uncertainty — `ChemistryUncertaintyAnalyzer`
Doses are quoted with far more precision than the inputs justify. Report a band.
```java
import neqsim.process.chemistry.util.ChemistryUncertaintyAnalyzer;
ChemistryUncertaintyAnalyzer unc = new ChemistryUncertaintyAnalyzer();
unc.setNumberOfTrials(2000);
unc.setRandomSeed(42L);
unc.addParameter(unc.triangular("baseRateMmYr", 1.5, 2.4, 4.0));
unc.addParameter(unc.triangular("shearPa", 20.0, 35.0, 60.0));
unc.run(new java.util.function.ToDoubleFunction<double[]>() {
public double applyAsDouble(double[] x) {
CorrosionInhibitorPerformance m = new CorrosionInhibitorPerformance();
m.setChemistry(CorrosionInhibitorPerformance.InhibitorChemistry.IMIDAZOLINE);
m.setDoseMgL(50.0);
m.setBaseCorrosionRateMmYr(x[0]);
m.setWallShearStressPa(x[1]);
m.setTemperatureCelsius(70.0);
m.evaluate();
return m.getInhibitedCorrosionRateMmYr();
}
});
double p10 = unc.getP10();
double p50 = unc.getP50();
double p90 = unc.getP90();
List<Map<String, Object>> tornado = unc.getTornado();
```
Feed `p10/p50/p90` and `tornado` straight into the `uncertainty` block of `results.json`
(`neqsim-professional-reporting`). Java 8: use an anonymous `ToDoubleFunction`, not a lambda,
if the surrounding code style requires it — both compile.
## 10. MCP Tools
| Tool | `analysis` values |
|------|-------------------|
| `runChemistry` | `electrolyteScale`, `multiMineralScale`, `electrolyteScaleEquilibrium`, `electrolyteMultiScaleEquilibrium`, `mechanisticCorrosion`, `langmuirInhibitor`, `packedBedScavenger`, `pitzerQualification` |
| `runFlowAssurance` | `scalePrediction`, `erosion`, `pipelineCooldown`, `emulsionViscosity`, `demulsifierDoseOptimization`, hydrate analyses |
```json
{
"analysis": "mechanisticCorrosion",
"temperature_C": 60, "pressure_bara": 80, "co2_mol": 0.05,
"velocity_ms": 2.0, "diameter_m": 0.15, "dose_mgL": 50
}
```
## Python Pattern (task notebooks)
```python
ProductionChemical = ns.JClass("neqsim.process.chemistry.ProductionChemical")
Assessor = ns.JClass("neqsim.process.chemistry.ChemicalCompatibilityAssessor")
CIP = ns.JClass("neqsim.process.chemistry.corrosion.CorrosionInhibitorPerformance")
si = ProductionChemical.scaleInhibitor("SI-A", 20.0)
ci = ProductionChemical.corrosionInhibitor("CI-B", 50.0)
a = Assessor()
a.addChemical(si); a.addChemical(ci)
a.setTemperatureCelsius(85.0); a.setCalciumMgL(1200.0)
a.evaluate()
import json
report = json.loads(str(a.toJson()))
print(report["verdict"])
```
Enums through jpype: `CIP.InhibitorChemistry.valueOf("IMIDAZOLINE")`.
## Offshore Workflow
1. **Basis** — build the produced-water stream (`SystemElectrolyteCPAstatoil`) from the ion
analysis; get T/P/rates from the flowsheet or historian.
2. **Threat magnitude** — SI, uninhibited corrosion rate, hydrate subcooling, WAT, CII
(`neqsim-flow-assurance`, `neqsim-wax-calculations`).
3. **Chemical selection + dose** — the §3 performance models.
4. **Compatibility of the full cocktail** — §2. Repeat per injection point, because the
cocktail differs at the wellhead, the manifold and the topside inlet.
5. **Second-order effects** — §6 (does the pH adjuster create a scale problem?), §5 (does the
demulsifier meet the OiW spec?).
6. **Uncertainty + gaps** — §9 plus `getWarnings()` / `getDataGaps()` from every model.
7. **Report** — `standards_applied`, `key_results`, `uncertainty`, `risk_evaluation`.
## Agent Cooperation
| Need | Route to |
|------|----------|
| Hydrate curve, WAT, SI, corrosion rate | `neqsim-flow-assurance`, `neqsim-wax-calculations` |
| Building the brine / electrolyte fluid | `neqsim-electrolyte-systems` |
| Material limits, sour service | `neqsim-standards-lookup`, `enterprise-materials-selection-screening` |
| MEG loop mass balance, regeneration | `enterprise-meg-loop` |
| Measured dose / OiW / H2S tags | `neqsim-plant-data` |
| Equipment-level RCA from historian trends | `neqsim-root-cause-analysis` |
| Report structure, results.json schema | `neqsim-professional-reporting` |
## Gotchas
| Symptom | Cause | Fix |
|---------|-------|-----|
| All getters return 0 | `evaluate()` not called | Setters → `evaluate()` → getters. `isEvaluated()` tells you. |
| Compatibility verdict COMPATIBLE with an obviously bad pair | Chemicals lack `activeIngredient` / `ionicNature` | The rule base matches on type **and** ingredient; set both. |
| Scale/corrosion models return zeros from a stream | Stream has no aqueous phase or no ions | Build with `SystemElectrolyteCPAstatoil` and add ions; verify with `StreamChemistryAdapter`. |
| MIC far above the umbilical capacity | Saturation ratio, not SI, is the input | `setSaturationRatio()` expects SR (=10^SI), not the log. |
| CI efficiency collapses | O2 ppb or shear too high | Read `getWarnings()`; the fix is scavenger / ingress control, not more CI. |
| Hydrate injection rate disagrees with the flash | Hammerschmidt is a screening correlation | Use the CPA inhibited hydrate curve for design; use this for rate bookkeeping. |
| `InhibitorInjectionPoint` does not change pH/hydrate T | By design (no flash on run) | Pass `getActiveIngredientPpmInWater()` into the dedicated chemistry model. |
## Reference Documentation
- [docs/chemistry/index.md](../../../docs/chemistry/index.md) — capability matrix and standards
- [docs/chemistry/chemical_compatibility_guide.md](../../../docs/chemistry/chemical_compatibility_guide.md)
- [docs/chemistry/mechanistic_corrosion.md](../../../docs/chemistry/mechanistic_corrosion.md)
- [docs/chemistry/packed_bed_scavenger.md](../../../docs/chemistry/packed_bed_scavenger.md)
- Notebooks: `examples/notebooks/chemical_integrity_digital_twin.ipynb`,
`chemistry_corrosion_inhibitor_design.ipynb`
- Tests / regression baseline: `src/test/java/neqsim/process/chemistry/`