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.RootCauseAnalyserhere 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.
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.
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
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.
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.
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
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
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
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
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 toRootCauseAnalyser— it has an explicitH2S_SCAVENGER_UNDER_CAPACITY/ overdose candidate.
4. Injection Point as Flowsheet Equipment — InhibitorInjectionPoint
Makes chemical injection visible, snapshot-able and adjustable in a ProcessSystem.
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
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.
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.
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:
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:
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:
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.
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 |
{
"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)
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
- Basis — build the produced-water stream (
SystemElectrolyteCPAstatoil) from the ion analysis; get T/P/rates from the flowsheet or historian. - Threat magnitude — SI, uninhibited corrosion rate, hydrate subcooling, WAT, CII
(
neqsim-flow-assurance,neqsim-wax-calculations). - Chemical selection + dose — the §3 performance models.
- Compatibility of the full cocktail — §2. Repeat per injection point, because the cocktail differs at the wellhead, the manifold and the topside inlet.
- Second-order effects — §6 (does the pH adjuster create a scale problem?), §5 (does the demulsifier meet the OiW spec?).
- Uncertainty + gaps — §9 plus
getWarnings()/getDataGaps()from every model. - 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 — capability matrix and standards
- docs/chemistry/chemical_compatibility_guide.md
- docs/chemistry/mechanistic_corrosion.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/