- name
- neqsim-flow-assurance
- description
- Flow assurance patterns for NeqSim. USE WHEN: predicting hydrate, wax, asphaltene, CO2/H2S corrosion (NORSOK M-506), mineral scale and brine mixing, valve scale drift and remediation, sulfur S8 deposition, metal-loss screening, pipeline hydraulics, DNV-RP-F109/F105/F104/F110/F114 screening, water hammer, slug flow, thermal analysis or inhibitor dosing. Code patterns plus the governing standards for every threat.
- last_verified
- 2026-09-07
# Flow Assurance Analysis with NeqSim
Consolidated guide for all flow assurance threats — hydrate, wax, asphaltene, corrosion,
hydraulics, water/liquid hammer screening, slugging, and thermal management — with
NeqSim code patterns.
> A complete study report with pipeline drawings (route, profiles, hydrate margin, threat matrix,
> validation) is produced by `neqsim flow-assurance-report` — see the
> `neqsim-flow-assurance-study-report` skill. This skill stays the calculation reference.
> A whole subsea SURF network (templates, injection, risers, umbilical, concept comparison) is
> generated, solved and documented by `neqsim surf-study` — see the `neqsim-surf-design-study` skill.
## When to Use This Skill
- Hydrate formation temperature/pressure prediction
- Hydrate inhibitor dosing (MEG, methanol, ethanol)
- Wax appearance temperature (WAT) and wax deposition risk
- Asphaltene stability screening (de Boer, CII)
- CO2 and H2S corrosion rate estimation
- Elemental sulfur (S8) deposition risk at pressure/temperature letdown (compressor inlets, control/letdown valves, dry-gas seals, filters)
- Pipeline pressure drop and temperature profile
- DNV-RP-F109 vertical and lateral on-bottom stability screening
- Water hammer/liquid hammer screening for fast valve closure, pump trip, or check-valve slam
- Multiphase flow pattern prediction (slug, annular, stratified)
- Thermal insulation sizing for subsea pipelines
- Arrival temperature and cooldown calculations
## Applicable Standards
| Domain | Standards | Key Requirements |
|--------|-----------|-----------------|
| Pipeline design | DNV-ST-F101, DNV-RP-F104 for CO2, NORSOK L-001, ASME B31.4/B31.8 | Structural design plus composition-specific CO2 phase/hydraulic and lifecycle basis |
| Corrosion | NORSOK M-001, DNV-RP-F112, ISO 21457 | Material selection, CO2/H2S corrosion rates |
| On-bottom stability | DNV-RP-F109 | Vertical stability, absolute lateral stability, displacement acceptance |
| Free spans | DNV-RP-F105 | Free-span response and fatigue assessment |
| Global buckling and pipe-soil interaction | DNV-RP-F110, DNV-RP-F114 | Caller-controlled external response and demand-resistance screening |
| Subsea systems | NORSOK U-001 | Subsea production-system requirements |
| Hydrate management | DNV-RP-F116 | Hydrate prevention and remediation |
| GRP piping | ISO 14692 | Non-metallic pipe design |
| Pipeline integrity | DNV-RP-F101, DNV-RP-F116, API 1160 | Inspected metal-loss remaining strength and integrity management |
For fast acoustic transients, also load `neqsim-water-hammer`. Use
`WaterHammerStudy` or MCP `runWaterHammer` with STID route geometry, tagreader
event windows, and valve/pump event schedules; use this flow-assurance skill for
the broader operating-envelope and mitigation context.
For on-bottom stability, load `neqsim-subsea-and-wells` and use the typed
`DnvRpF109OnBottomStabilityKernel`. It provides a transparent absolute-static
screen and checks externally calculated response displacements. It does not
contain generalized design tables, produce dynamic response, qualify environmental
or soil models, or claim DNV conformity. A pass still requires independent review.
This skill quantifies the **threat** (hydrate curve, WAT, SI, corrosion rate). For the
**chemical** answer — which product, minimum effective dose, cocktail compatibility, H2S/O2
scavenger sizing, demulsifier vs oil-in-water spec, and chemical root-cause of a deposit —
load `neqsim-production-chemistry` (`neqsim.process.chemistry`) and feed it the numbers
computed here.
For local dry hydrocarbon gas/oil interface investigations, the existing film boundary
has a default-off `setCoupledMassHeatTransferEnabled(true)` numerical option. Read
`docs/fluidmechanics/InterphaseHeatMassTransfer.md` for its residual gates and limited
positive-composition/finite-transport range. It is not connected to the transient
`TwoFluidPipe` source. Zero liquid Reynolds number currently gives an unsupported
stratified transfer coefficient and rejects; never interpret the old unit-matrix
fallback as stationary-pool evaporation. Keep #4674 physical boundary/full-cycle and
#4675 heat/area/diffusion qualification separate from numerical convergence.
## 1. Hydrate Analysis
### EOS Selection for Hydrate Calculations
| Aqueous phase | NeqSim Class | Mixing Rule |
|---------------|-------------|-------------|
| Pure water / fresh water | `SystemSrkCPAstatoil` | `10` |
| Water + MEG / methanol / ethanol | `SystemSrkCPAstatoil` | `10` |
| **Salt brine / formation water (NaCl, CaCl2, ...)** | `SystemElectrolyteCPAstatoil` | `10` |
| Pitzer salt-brine water activity | `SystemPitzer` | `"classic"` |
CPA or a consistent activity-based aqueous model is needed for water–hydrocarbon hydrate modeling.
**When dissolved salts / electrolytes are present, use `SystemElectrolyteCPAstatoil` or a
parameterized `SystemPitzer`** so the salt
thermodynamic (hydrate-suppression) effect is captured — plain
`SystemSrkCPAstatoil` ignores ion activity and underestimates subcooling margin.
For Pitzer hydrate onset, load `neqsim-electrolyte-systems` and read
`docs/thermo/pitzer_hydrate_equilibrium.md`. Accept its hand-off of salt mole basis, dataset,
explicit CO2-chloride zeta values (and K-Mg theta when both ions are present), pressure grid and operating temperature. Use the standard
temperature/pressure/curve operations; the Pitzer route checks the fugacity residual and throws
on failed points. Preserve the aqueous reference-state convention and guest Henry limits.
Do not label onset calculations as hydrate amounts, kinetics or validated complete drilling-mud predictions.
Carry forward the experimental assessment separately from convergence: the mixed-chloride high-pressure cases
include errors exceeding 1 K. Do not generalize the NaCl benchmark to all salts, concentrations or pressures.
### Hydrate Formation Temperature
```java
// CPA EOS required for accurate water-hydrocarbon modeling
SystemInterface fluid = new SystemSrkCPAstatoil(273.15 + 10, 100.0);
fluid.addComponent("methane", 0.80);
fluid.addComponent("ethane", 0.05);
fluid.addComponent("propane", 0.03);
fluid.addComponent("CO2", 0.02);
fluid.addComponent("water", 0.10);
fluid.setMixingRule(10); // CPA mixing rule
fluid.setMultiPhaseCheck(true);
fluid.setHydrateCheck(true);
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.hydrateFormationTemperature();
double hydrateT_C = fluid.getTemperature() - 273.15;
```
`setHydrateCheck(true)` is mandatory: without it `hydrateFormationTemperature()` fails
(no hydrate phase) and any wrapper that swallows the exception reports NaN. The fluid must
contain `water`. `HydrateRiskMapper` (and MCP `runFlowAssurance` `hydrateRiskMap`) now sets the
flag itself and returns `RiskLevel.UNKNOWN` / `RESULT_NOT_AVAILABLE` with `failureReasons` when
no equilibrium temperature exists — never treat an unavailable hydrate temperature as safe.
### Hydrate Equilibrium Curve (Multiple Pressures)
```java
// Calculate hydrate T at several pressures for the full curve
double[] pressures = {20, 40, 60, 80, 100, 120, 150, 200};
double[] hydrateTemps = new double[pressures.length];
for (int i = 0; i < pressures.length; i++) {
SystemInterface testFluid = fluid.clone();
testFluid.setPressure(pressures[i]);
ThermodynamicOperations testOps = new ThermodynamicOperations(testFluid);
testOps.hydrateFormationTemperature();
hydrateTemps[i] = testFluid.getTemperature() - 273.15;
}
```
### Hydrate Inhibitor Dosing (MEG)
```java
// Add MEG to suppress hydrate formation temperature
SystemInterface inhibitedFluid = new SystemSrkCPAstatoil(273.15 + 4, 100.0);
inhibitedFluid.addComponent("methane", 0.80);
inhibitedFluid.addComponent("water", 0.15);
// 25 wt% MEG in the water + MEG phase: n_MEG = n_water * 18.015 * 0.25 / 0.75 / 62.07 = 0.0145 mol
// (0.05 mol MEG per 0.15 mol water would be 53 wt%: amounts are MOLES, never weight percentages)
inhibitedFluid.addComponent("MEG", 0.0145);
inhibitedFluid.setMixingRule(10);
inhibitedFluid.setMultiPhaseCheck(true);
inhibitedFluid.setHydrateCheck(true);
ThermodynamicOperations ops = new ThermodynamicOperations(inhibitedFluid);
ops.hydrateFormationTemperature();
double inhibitedHydrateT = inhibitedFluid.getTemperature() - 273.15;
// Compare with uninhibited to get subcooling margin
```
For the **injection rate** that delivers a required wt% in the water phase (including purity
and lean/rich MEG bookkeeping), use
`neqsim.process.chemistry.hydrate.ThermodynamicHydrateInhibitorPerformance` — see
`neqsim-production-chemistry`. Use the CPA curve above for the design concentration and the
chemistry model for the rate.
### Salt-Inhibited Hydrate (Formation Water / Brine)
```java
// Dissolved salts depress the hydrate temperature — use the electrolyte CPA model
SystemInterface brineGas = new SystemElectrolyteCPAstatoil(273.15 + 4, 100.0);
brineGas.addComponent("methane", 0.80);
brineGas.addComponent("water", 0.18);
brineGas.addComponent("Na+", 0.01); // dissociated NaCl
brineGas.addComponent("Cl-", 0.01);
brineGas.setMixingRule(10);
brineGas.setMultiPhaseCheck(true);
brineGas.setHydrateCheck(true);
ThermodynamicOperations ops = new ThermodynamicOperations(brineGas);
ops.hydrateFormationTemperature();
double brineHydrateT = brineGas.getTemperature() - 273.15;
// SystemSrkCPAstatoil would miss the salt suppression — always use electrolyte CPA with ions
```
### MEG Concentration Sweep
```java
// Find required MEG concentration for target subcooling
double[] megWtPct = {0, 10, 20, 30, 40, 50};
for (double wt : megWtPct) {
// wt % MEG is a MASS fraction of the water + MEG mixture: convert to moles (M_water 18.015, M_MEG 62.07 g/mol)
double waterMol = 0.20 * (1.0 - wt / 100.0) / 18.015;
double megMol = 0.20 * (wt / 100.0) / 62.07;
double waterFrac = waterMol / (waterMol + megMol) * 0.20;
double megFrac = megMol / (waterMol + megMol) * 0.20;
SystemInterface testFluid = new SystemSrkCPAstatoil(273.15, 100.0);
testFluid.addComponent("methane", 0.80);
testFluid.addComponent("water", waterFrac);
testFluid.addComponent("MEG", megFrac);
testFluid.setMixingRule(10);
testFluid.setHydrateCheck(true);
ThermodynamicOperations testOps = new ThermodynamicOperations(testFluid);
testOps.hydrateFormationTemperature();
// Record hydrate T vs MEG concentration
}
```
**Sweep gotchas (found building an HPHT tie-back, Fogelberg VPbo task):** `hydrateFormationTemperature()`
with a rich gas condensate (C7+ pseudo-components) plus water and MEG at 40 wt % MEG did not return
(CPA hydrate solver loop, no exception, no timeout). Run every MEG/pressure point of a sweep in a
watchdog thread or subprocess with a hard time limit and keep the sweep to at most 30 wt % MEG. If a
40-50 wt % row is still needed, a linear extrapolation (about -4.5 K per 10 wt % at 30-400 bara) is an
UNVALIDATED screening number: label it as extrapolated, never use it for a pass/fail margin, and do not
let the whole script hang for it. Write the intermediate JSON before the hydrate loop so a kill does not lose the
fluid and CVD results.
For flowline hydraulics with `PipeBeggsAndBrills` a trial inlet pressure that is too low throws
`HydraulicDomainException: Non-positive pipeline pressure` instead of returning a low outlet pressure:
catch it inside the chain function and return outlet 0 (infeasible). Letting it propagate out of a
bisection silently turns a whole row of the inlet-pressure table into NaN.
`ThermodynamicOperations.hydrateFormationTemperature()` is `void`: the answer is left in the fluid
temperature, so read `fluid.getTemperature() - 273.15` after the call (`float(ops.hydrateFormationTemperature())`
fails with `NoneType`; found in the Mistral Nord VPbo task). A wellstream built with `SystemSrkEos` plus
`setHydrateCheck(true)` also under-predicts the hydrate temperature (methane benchmark about -8 K): rebuild
the composition in `SystemSrkCPAstatoil` (mixing rule 10) and benchmark methane at 43/76/140 bara against
5/10/15 C before using the numbers (CPA gives -0.03/+0.33/+0.58 K).
To benchmark `PipeBeggsAndBrills` against Darcy-Weisbach use a dry single-phase gas (pure methane, 60 C,
`setHeatTransferCoefficient` large): the deviation is then 0.2-0.4 %. A lean-gas condensate line cooled
to seabed temperature differs by 25-50 % because the liquid holdup is real, so that difference is not a defect.
Gas-condensate saturation pressure (Tyrihans cellar task): a bubble-point search on a lean gas-condensate fluid returns
a wrong value because the fluid has a dew point; take the saturation pressure of GOR above about 1500 Sm3/Sm3 fluids
from the CVD first step (dew point) and record it as a limitation. `hydrateFormationTemperature()` on a GOR 250 oil fluid
with 3 mol % water at 200 bara did not converge and left the temperature unchanged: check that the returned temperature
differs from the start temperature before reporting it and report `None` as a gap, not as a hydrate-free result.
HPHT wellstream hydrates (Erlend / Kristin study): the CPA hydrate solver also stalls (no exception) with the
C7+ TBP/plus pseudo-components at 50 bara and 1 mol % water, even without MEG. Rebuild the CPA mixture from the
light components only (C1-C6, N2, CO2, water, renormalised), as C7+ barely moves the hydrate temperature; a
Kristin-type gas (CGR 300 Sm3/MSm3) with 1 mol % water gave 17.2 / 21.2 / 26.6 / 31.9 C at 50 / 100 / 300 / 600 bara.
Do not iterate `PipeBeggsAndBrills` inlet pressure by a fixed point `p_in += p_out_target - p_out`: it stalls at high
pressure drop (unconverged tables looked like data). Use a secant solve, treat a `HydraulicDomainException` as outlet
pressure 0, store an `ok` flag and exclude `ok == False` rows when interpolating; beyond the last converged rate
extend the table with dp proportional to q^2 instead of the last slope.
### MEG/Water Film Conditioned by Dry Gas (pipeline drying after glycol swabbing)
Question pattern: "what MEG:H2O ratio does a residual film reach under dry export gas, and why is
the gas above the dew-point spec?" Worked task: `2026-09-29_steady_state_megh2o_ratio_during_dry_gas_drying_of_a_meg`.
- **Direction:** a 99 wt% MEG film is *drier* than the plateau (about 90 wt% at 6 C, 140 bara), so the
gas is the wetter side and the film **absorbs** water. It "dries" only if it starts wetter than the plateau.
- **Equilibrium curve:** two-phase `SystemSrkCPAstatoil` TPflashes over an MEG sweep give gas water *and MEG*
content versus film composition (gas composition depends on the liquid composition, T and P only).
Report film composition on a **gas-free** basis, MEG/(MEG+water); dissolved CO2/CH4 is ~0.8 wt%.
MEG is not strictly non-volatile: about 5 ppm wt in the gas at 140 bara.
- **Dew-point spec fixes only the water mole fraction** y_in, not the pipe condition. Hand chain: water
fugacity continuity, `a_w = y_in * phiV_w(T,P) * P / f0_w(T,P)`, with `f0_w` = IAPWS vapour pressure x
Poynting; `a_w` from the CPA liquid fugacity coefficient divided by the pure-water coefficient. Use binary
(gas-free) mole fractions when fitting `ln gamma_w` or the fit mis-predicts at high MEG.
- **Plateau offset by mass balance:** with a stationary film the gas leaves carrying water and MEG in the
film's own ratio, so `(y_out - y_in)/y_in = r * y_MEG / y_in` (about 1.7 %), independent of `K_G` at large NTU.
The film inlet zone is stripped of MEG first (a film-free zone grows at ~200 m/day in a 0.4 m, 60 kg/s example).
- **Flash per time step is backward Euler:** unconditionally stable, plateau independent of step size; an
explicit coding is stable only below `2 / (d y*_H2O / d r)` kg gas per kg film MEG per step.
- **Dew point is logarithmic** (~6 % in water content per K at these conditions) and, for CPA, nearly
pressure independent at 70-140 bara: reading the same gas as a dew point at pipe pressure adds ~0.9 K.
- **Basis traps:** CPA liquid-water dew point is a supercooled-liquid reference below freezing;
ISO 18453 / GERG-water uses an ice reference below 273.16 K. These are different phase boundaries.
For GERG-water, use `SystemGERGwaterEos`, `setMixingRule(8)`, and a
`ThermodynamicOperations` instance's `waterDewPointTemperatureFlash()`; follow the ISO 18453 execution rule
in `neqsim-standards-lookup` and the [dew-point guide](../../../docs/standards/dew_point_standards.md).
Recalculate outputs from the former non-convergent solver before comparing models. Neither an
ideal-gas estimate nor a CPA/GERG difference alone identifies an ISO implementation error.
- **Numerics:** for N >= 100 film cells use `solve_ivp(method="BDF")`; LSODA stalls on the film-exhaustion
switch. Regularise `r = W/(M + delta)` so a film-free cell cannot return a spurious composition.
## 2. Wax Analysis
### Wax Appearance Temperature (WAT)
```java
// Oil system with C7+ fractions for wax prediction
SystemInterface oil = new SystemSrkEos(273.15 + 60, 50.0);
oil.addComponent("methane", 0.30);
oil.addComponent("ethane", 0.10);
oil.addTBPfraction("C7", 0.10, 92.0 / 1000, 0.727);
oil.addTBPfraction("C10", 0.15, 134.0 / 1000, 0.78);
oil.addTBPfraction("C15", 0.15, 206.0 / 1000, 0.83);
oil.addPlusFraction("C20", 0.20, 350.0 / 1000, 0.88);
oil.getCharacterization().setWaxModel(true);
oil.getCharacterization().characterisePlusFraction();
oil.setMixingRule("classic");
ThermodynamicOperations ops = new ThermodynamicOperations(oil);
ops.calcWAT();
double wat_C = oil.getTemperature() - 273.15;
```
### Wax Fraction vs Temperature (PVT Simulation)
```java
import neqsim.pvtsimulation.simulation.WaxFractionSim;
WaxFractionSim waxSim = new WaxFractionSim(oil);
waxSim.setTemperatures(new double[]{333.15, 313.15, 293.15, 273.15});
waxSim.run();
double[] waxFractions = waxSim.getWaxFraction();
GitHubで見る