NeqSim API Patterns
Copy-paste reference for common NeqSim operations. All Java code must be Java 8 compatible.
MCP Runtime Capability Routing
When a calculation is not exposed by a curated MCP domain tool, use the generic runtime index before proposing a new tool:
- Search with
runCapability({"action":"search","query":"<domain method>"}). - Inspect the selected class with
inspectApito pin the deployed signature. - Follow the returned route:
static-json: invoke throughrunCapabilitywith exactclassName,methodName,parameterTypes, and ordered JSONarguments.process-json: build the stateful equipment throughrunProcess.inspect-only: use a curated tool or add an explicit, reviewed adapter.
Discovery is broader than execution by design. runCapability only invokes public static methods
in approved domain packages with scalar, enum, or bounded-array JSON types; MCP runners, raw generic
containers, oversized payloads, arbitrary objects, and instance methods are excluded. Its timeout
uses cooperative Java interruption, so route long-running calculations through a curated runner or
runProcess. Treat runtime presence as capability evidence, then check tests, benchmark trust, and
standards before using the result for engineering decisions.
EOS Selection Guide
| Fluid Type | Java Class | Mixing Rule |
|---|---|---|
| Dry/lean gas, simple HC | SystemSrkEos |
"classic" |
| General hydrocarbons, oil | SystemPrEos |
"classic" |
| Matched to commercial simulator PR-LK | SystemPrLeeKeslerEos |
"classic" |
| Water, MEG, methanol, polar | SystemSrkCPAstatoil |
10 (numeric) |
| Custody transfer, fiscal metering | SystemGERG2008Eos |
(none needed) |
| Electrolyte systems, hydrate with salt brine | SystemElectrolyteCPAstatoil |
10 |
| Volume-corrected SRK | SystemSrkEosvolcor |
"classic" |
PR-LK vs PR78: SystemPrLeeKeslerEos uses PR76 alpha for ALL ω:
m = 0.37464 + 1.54226ω − 0.26992ω². Standard SystemPrEos1978 uses a modified
cubic for ω > 0.49. Use PR-LK when matching commercial simulator models that use
this EOS label.
Fluid Creation (Required Sequence)
// 1. Create: temperature in KELVIN, pressure in bara
SystemInterface fluid = new SystemSrkEos(273.15 + 25.0, 60.0);
// 2. Add components (name, mole fraction)
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
// 3. MANDATORY: set mixing rule — NEVER skip
fluid.setMixingRule("classic");
// 4. Optional: multi-phase check for water/heavy systems
fluid.setMultiPhaseCheck(true);
Oil Characterization (C7+ Fractions)
fluid.addTBPfraction("C7", 0.05, 92.0 / 1000, 0.727); // name, moleFrac, MW_kg/mol, density
fluid.addTBPfraction("C8", 0.04, 104.0 / 1000, 0.749);
fluid.addPlusFraction("C20+", 0.02, 350.0 / 1000, 0.88);
fluid.getCharacterization().getLumpingModel().setNumberOfLumpedComponents(6);
fluid.getCharacterization().characterisePlusFraction();
Loading Fluids from E300 Files
NeqSim can read Eclipse E300-format fluid files with full component properties and binary interaction parameters:
// Load fluid from E300 file (returns SystemInterface with PR-EOS)
SystemInterface fluid = EclipseFluidReadWrite.read("path/to/fluid.e300");
// Returns a PR-EOS fluid with all components, properties, and BIPs set
Required E300 sections: CNAMES, TCRIT, PCRIT, ACF, MW, TBOIL,
VCRIT, PARACHOR, SSHIFT, BIC, ZI.
Optional E300 sections (NeqSim parses and applies these):
OMEGAA/OMEGAB— per-component OmegaA/B overrides (applied afterinit(0))BICS— surface-condition BICs (parsed, same lower-triangular format asBIC)SSHIFTS— surface-condition volume shiftPEDERSEN— activates Pedersen viscosity model
EOS keyword determines fluid class:
EOS\nSRK /→SystemSrkEosEOS\nPR /\nPRCORR→SystemPrEos1978EOS\nPR /\nPRLKCORR→SystemPrLeeKeslerEos← use for PR-LK matchingEOS\nPR /→SystemPrEos
CRITICAL: The BIC section must ALWAYS be present. If omitted, NeqSim
defaults to zero BIPs (no crash, but results may differ significantly from the
source simulator). The PARACHOR section is also required — estimate unknown
values with 4.0 * MW^0.77.
Component name mapping: C1→methane, C2→ethane, C3→propane,
iC4→i-butane, C4→n-butane, iC5→i-pentane, C5→n-pentane,
C6→n-hexane, N2→nitrogen, CO2→CO2, H2O→water. All other names are
treated as TBP pseudo-fractions via addTBPfraction() — including aromatics
(Benzene, Toluene, etc.).
# Python usage — auto-detects EOS from file
from neqsim import jneqsim
EclipseFluidReadWrite = jneqsim.thermo.util.readwrite.EclipseFluidReadWrite
fluid = EclipseFluidReadWrite.read("path/to/fluid.e300")
# Force a specific EOS regardless of what's in the file
SystemPrLeeKeslerEos = jneqsim.thermo.system.SystemPrLeeKeslerEos
target_fluid = SystemPrLeeKeslerEos(288.15, 1.01325)
fluid = EclipseFluidReadWrite.read("path/to/fluid.e300", target_fluid)
JSON process builder also supports PR-LK via "model": "PR_LK":
{ "fluid": { "model": "PR_LK", "temperature": 288.15, "pressure": 50.0, ... } }
Flash Calculations and Property Retrieval
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.TPflash();
// CRITICAL: call initProperties() AFTER flash, BEFORE reading properties
// init(3) alone does NOT initialize transport properties — they return ZERO
fluid.initProperties();
// Bulk properties
double density = fluid.getDensity("kg/m3");
double molarMass = fluid.getMolarMass("kg/mol");
double Z = fluid.getZ();
// Phase properties
double gasDensity = fluid.getPhase("gas").getDensity("kg/m3");
double gasViscosity = fluid.getPhase("gas").getViscosity("kg/msec");
double gasThermalCond = fluid.getPhase("gas").getThermalConductivity("W/mK");
double gasCp = fluid.getPhase("gas").getCp("J/kgK");
// Phase checks
int numPhases = fluid.getNumberOfPhases();
boolean hasGas = fluid.hasPhaseType("gas");
Other Flash Types
ops.PHflash(enthalpy); // Pressure-Enthalpy
ops.PSflash(entropy); // Pressure-Entropy
ops.dewPointTemperatureFlash(); // Dew point temperature
ops.bubblePointPressureFlash(); // Bubble point pressure
ops.hydrateFormationTemperature(); // Hydrate T at given P
ops.calcPTphaseEnvelope(); // Phase envelope
Saturation flashes can fail without throwing. A failed continuation in
dewPointPressureFlash()/bubblePointPressureFlash()can leave a non-physical pressure on the system and return normally. In a single run someone notices; inside a Monte Carlo loop it silently poisons a percentile. Validate the result on physical grounds instead of trusting the absence of an exception:ops.dewPointPressureFlash() p_dew = fluid.getPressure() if not (10.0 < p_dew < 3.0 * p_reservoir): p_dew = float("nan") # reject, do not propagate
Re-flashing a characterised fluid many times (CRITICAL for loops)
To take a phase's composition and flash it somewhere else — produced gas at each
depletion step, a recycle stream, a Monte Carlo realization — clone the already
characterised fluid and overwrite its composition. Do NOT rebuild it with
addTBPfraction: that re-runs the TBP characterisation on every call.
probe = fluid.clone()
probe.setTemperature(T_res + 273.15)
probe.setPressure(p)
ns.ThermodynamicOperations(probe).TPflash()
probe.initProperties()
gas_phase = probe.getPhase("gas")
produced = fluid.clone() # keeps the characterisation
produced.setMolarComposition(
[gas_phase.getComponent(i).getx()
for i in range(probe.getNumberOfComponents())])
produced.setTemperature(288.15)
produced.setPressure(1.01325)
ns.ThermodynamicOperations(produced).TPflash() # -> CGR, gas gravity, etc.
Measured on a 26-component P/A gas condensate with nine pressure nodes: the
addTBPfraction rebuild cost 4.96 s per realization against 0.97 s for
the clone-and-overwrite route, for bit-identical results. Over a 4000-member
ensemble that is the difference between three hours and four minutes — i.e.
between propagating uncertainty through the model and deciding not to.
Unit Conventions
| Quantity | Constructor default | Setter pattern |
|---|---|---|
| Temperature | Kelvin | setTemperature(25.0, "C") |
| Pressure | bara | setPressure(50.0, "bara") |
| Flow rate | — | setFlowRate(50000.0, "kg/hr") |
| Getting temp | Returns Kelvin | getTemperature() - 273.15 for °C |
Non-obvious return units
| Call | Returns | Trap |
|---|---|---|
Standard_ISO6976(sys, 15, 15, "volume").getValue("GCV") |
kJ/Sm³ (~40 000) | Dividing by 1e6 gives a nonsense 0.04 MJ/Sm³ — divide by 1e3 |
Standard_ISO6976(...).getValue("WI") |
kJ/Sm³ | Same |
SURFCostEstimator.setContingencyPct(x) |
— | Takes a fraction (0.35), not a percent, despite the name. Same for WellCostEstimator |
Cooler.getDuty() |
W | Divide by 1e3 for kW |
Dense-phase CO₂ needs GERG-2008, not a cubic
Benchmarked against CoolProp (Span-Wagner) — density deviation at 40 °C / 100 bara and 100 °C / 200 bara:
| System class | Deviation |
|---|---|
SystemSrkEos |
−14.1 % / −7.3 % |
SystemPrEos, SystemPrEos1978, SystemUMRPRUMCEos |
−12.4 % / −6.1 % |
SystemSrkCPAstatoil |
−17.5 % / −10.3 % |
SystemGERG2008Eos |
+0.01 % / +0.02 % |
Use SystemGERG2008Eos for any CO₂ compression, injection or transport duty. Cubics are
acceptable for the gas-phase part of the train but not near or above the critical density.
Process Equipment Patterns
Standard outlet-stream contract
Equipment that produces phase-separated gas and liquid products must expose the
conventional getGasOutStream() and getLiquidOutStream() methods. A
three-phase unit should also expose its conventional water outlet. Domain names
such as getOverheadGasStream(), getLeanLiquidStream(), or
getBottomsStream() are useful aliases, but they supplement rather than replace
the conventional accessors and must return the same stream objects.
Every equipment class must also report all connected streams through
getInletStreams() and getOutletStreams(). These topology lists must contain
the live public stream objects, not clones or solver-internal tray streams. Once
an outlet has been handed to downstream equipment, preserve its object identity
across run(...) calls by updating its thermodynamic system in place or using
the established identity-preserving adoption helper.
For phase-separated equipment, add a focused contract test after a successful solve that verifies:
assertSamebetween conventional accessors, domain aliases, and the matching entries ingetOutletStreams();- the gas product contains a
gasphase and the liquid product contains anoil,liquid, oraqueousphase; - expected product flows are positive and total/per-component balances close;
- outlet identity remains unchanged after a warm rerun or changed feed.
Do not add a new equipment-wide interface solely for gas/liquid naming. The
generic topology contract belongs to ProcessEquipmentInterface; conventional
phase-product methods belong on the phase-separating equipment abstraction.
Stream
Stream feed = new Stream("feed", fluid);
feed.setFlowRate(100.0, "kg/hr");
feed.setPressure(50.0, "bara");
feed.setTemperature(30.0, "C");
Separator
Separator sep = new Separator("HP Sep", feedStream);
Stream gasOut = sep.getGasOutStream();
Stream liqOut = sep.getLiquidOutStream();
Separator class ↔ orientation (affects gas-capacity results):
| Class | Default orientation | Use for |
|---|---|---|
Separator, ThreePhaseSeparator |
horizontal | horizontal separators (VA-tag) |
GasScrubber, GasScrubberSimple, NeqGasScrubber (2-phase) |
vertical | vertical scrubbers (VG-tag) |
ThreePhaseGasScrubber (3-phase) |
vertical | vertical 3-phase scrubbers |
A horizontal vessel derates the gas area by the design liquid level (default 80% →
gas area (1−0.8)=0.2×), so using a horizontal Separator/ThreePhaseSeparator
for a physically vertical scrubber over-reads getGasLoadFactor() /
getGasSuperficialVelocity() by ~5×. Prefer the *GasScrubber classes for vertical
scrubbers, or override with sep.setOrientation("vertical"). setInternalDiameter()
propagates correctly through run() — the trap is orientation, not diameter.
Separator Mechanical Design (Physical Configuration)
Physical dimensions, internals, and design parameters are configured through
SeparatorMechanicalDesign — NOT directly on Separator. The Separator
class handles process simulation (flash, entrainment); SeparatorMechanicalDesign
owns the physical vessel design.
// After process.run():
sep.initMechanicalDesign();
SeparatorMechanicalDesign design =
(SeparatorMechanicalDesign) sep.getMechanicalDesign();
// Design envelope
design.setMaxOperationPressure(85.0); // bara
design.setMaxOperationTemperature(273.15 + 80); // K
// Vessel sizing parameters (configured via MechanicalDesign)
design.setGasLoadFactor(0.107); // K-factor [m/s]
design.setRetentionTime(120.0); // Liquid retention [s]
design.setFg(0.5); // Gas area fraction
// Nozzle diameters (set via MechanicalDesign, NOT on Separator)
design.setInletNozzleID(0.254); // 10-inch inlet nozzle [m]
design.setGasOutletNozzleID(0.20); // Gas outlet [m]
design.setOilOutletNozzleID(0.15); // Oil outlet [m]
// Demister/mist eliminator parameters
design.setDemisterType("wire_mesh"); // "wire_mesh", "vane_pack", "cyclone"
design.setDemisterPressureDrop(1.5); // [mbar]
design.setDemisterThickness(150.0); // [mm]
design.setFoamAllowanceFactor(1.0); // 1.0 = no foam
// Bridge methods — entrainment internals (delegate to Separator)
design.setInletPipeDiameter(0.254); // Inlet pipe ID for DSD generation [m]
design.setInletDeviceType(InletDeviceModel.InletDeviceType.INLET_VANE);
design.setGasLiquidSurfaceTension(0.020); // Interfacial tension [N/m]
design.addSeparatorSection("Demister", "meshpad");
// Bridge methods — dynamic internals (delegate to Separator)
design.setWeirHeightAbsolute(0.30); // Weir height [m] (syncs weirFraction)
design.setWeirLength(1.5); // Weir crest length [m]
design.setBootVolume(2.0); // Boot/sump volume [m3]
design.setMistEliminatorDpCoeff(150.0); // Euler number for dP calc
design.setMistEliminatorThickness(0.15); // Demister pad thickness [m]
// Run design calculation
design.readDesignSpecifications();
design.calcDesign();
String report = design.toJson();
// Results: design.getInnerDiameter(), design.getTantanLength(),
// design.getWallThickness(), design.getInletNozzleID(), etc.
Separator Entrainment (Carry-Over)
Imperfect separation is modelled with setEntrainment() on the Separator /
ThreePhaseSeparator itself (not the mechanical design). It transfers a fraction
of one phase into another outlet stream.
// setEntrainment(double val, String specType, String specifiedStream,
// String phaseFrom, String phaseTo)
// specType : "mole" | "mass" | "volume"
// specifiedStream : "feed" (fraction of feed) | "product" (fraction of receiving outlet)
// phaseFrom/To : "gas" | "oil" | "aqueous" (base Separator also accepts "liquid")
ThreePhaseSeparator sep = new ThreePhaseSeparator("1st Stage", feed);
// Liquid carry-over into gas (feed basis)
sep.setEntrainment(0.001, "mole", "feed", "oil", "gas"); // oil-in-gas
sep.setEntrainment(0.001, "mole", "feed", "aqueous", "gas"); // water-in-gas
// Cross-contamination expressed on the receiving product stream
sep.setEntrainment(0.005, "mass", "product", "aqueous", "oil"); // 0.5 mass% BS&W in oil
sep.setEntrainment(500e-6, "mass", "product", "oil", "aqueous"); // 500 ppm oil-in-water
sep.run();
- Base
Separatorsupports 3 paths:oil→gas,aqueous→gas,gas→liquid. ThreePhaseSeparatorsupports all 6 paths:oil→gas,aqueous→gas,gas→oil,gas→aqueous,oil→aqueous,aqueous→oil.- With
specifiedStream="product",valis clamped:≤0transfers nothing,≥1transfers the entire source phase.
Typical screening values (indicative only — always defer to the project
separation spec / datasheet; for rigorous physics use the enhanced entrainment
model and SeparatorMechanicalDesign):
| Carry-over path | Typical range | Basis | Notes |
|---|---|---|---|
| Liquid-in-gas (oil or water → gas) | 0.01 – 0.5 % | mole/mass, feed | Well-designed mist extractor; tighter (<0.01%) with high-efficiency internals |
| Gas carry-under (gas → liquid) | 0.1 – 2 % | mole, feed | Higher with foaming / short retention |
| Water-in-oil (BS&W, aqueous → oil) | 0.5 – 5 vol% | volume, product | Export crude spec often ≤ 0.5 vol%; inter-stage higher |
| Oil-in-water (oil → aqueous) | 100 – 1000 ppm | mass, product | Produced-water inlet; overboard discharge typically ≤ 30 ppm (OSPAR) |
Separation Efficiency Report (K-Factor Operating Windows)
SeparatorMechanicalDesign.calculateSeparationEfficiency() returns a
SeparatorEfficiencyReport that combines the physics-based entrainment /
carry-under fractions with a per-internal Souders-Brown K-factor operating
window check (from the internals database MinKFactor/MaxKFactor). It answers
"is this mist mat / vane pack / cyclone inside its good performance band, below
turndown, or into flooding?" and works for two-phase AND three-phase separators
and gas scrubbers (GasScrubberMechanicalDesign inherits it).
It is read-only — it does not change what run() does. Whether the physics
entrainment model is applied at run time is a separate opt-in toggle
(setEfficiencyModelEnabled). Default behaviour (no entrainment, or manual
setEntrainment(...)) is unchanged.
sep.run(); // flash
SeparatorMechanicalDesign design =
(SeparatorMechanicalDesign) sep.getMechanicalDesign();
design.calcDesign();
design.setDesign(); // push sized diameter to the separator
// Optional: pick a specific database sub-type for the mist mat
design.setDemisterType("wire_mesh"); // "wire_mesh" | "vane_pack" | "cyclone"
design.setDemisterSubType("High Efficiency"); // sub-type from SeparatorInternals.csv
// Read-only assessment (2-phase or 3-phase, auto-detected)
SeparatorEfficiencyReport report = design.calculateSeparationEfficiency();
double opK = report.getOperatingKFactor(); // m/s
double effGL = report.getOverallGasLiquidEfficiency(); // 0-1
String verdict = report.getVerdict(); // GOOD_PERFORMANCE | BELOW_TURNDOWN | FLOODING_RISK | MARGINAL_EFFICIENCY
for (InternalOperatingWindow w : report.getWindows()) {
// w.getStatus(): BELOW_MIN_TURNDOWN | IN_RANGE | ABOVE_MAX_FLOODING
// w.getMinKFactor(), w.getMaxKFactor(), w.getUtilization(), w.getTurndownRatio()
}
String json = report.toJson(); // full report incl. per-internal windows
// Apply the physics entrainment/carry-under model during run() (opt-in):
design.setEfficiencyModelEnabled(true); // delegates to setDetailedEntrainmentCalculation(true)
sep.run(); // gas/liquid outlets now reflect computed carry-over
design.setEfficiencyModelEnabled(false); // back to no-entrainment / manual setEntrainment
K-factor window meaning (limits from SeparatorInternals.csv):
K < Kmin → below turndown (poor coalescence, droplets slip through);
Kmin ≤ K ≤ Kmax → good performance band; K > Kmax → flooding / re-entrainment.
Compressor
Compressor comp = new Compressor("Comp", gasStream);
comp.setOutletPressure(120.0);
// comp.setIsentropicEfficiency(0.75);
Stream out = comp.getOutletStream();
// After run: comp.getPower("kW")
Compressor chart library (multiple named/selectable charts)
A Compressor can hold several performance maps at once via a
CompressorChartLibrary and switch the active chart by name — the professional
way to keep vendor-expected, as-tested and field-fitted curves for the same
machine side by side (revamp studies, digital twins, design-vs-tested checks).
See the Compressor Chart Library doc.
comp.addChart("BCL405B-design", expectedChart);
comp.addChart("BCL405B-tested", asTestedChart,
new CompressorChartMetadata("BCL 405/B", "gas export", "27-KA01",
"8300199-CA-001", CompressorChartMetadata.CurveType.AS_TESTED));
comp.selectChart("BCL405B-tested"); // sets + enables the chart, turns on polytropic calc
comp.run();
List<String> charts = comp.getAvailableCharts(); // ["BCL405B-design", "BCL405B-tested"]
String active = comp.getSelectedChartName(); // "BCL405B-tested"
// Persist / reload a shared vendor-curve database (all curves + metadata):
comp.getChartLibrary().saveToFile("BCL405B_charts.json");
comp.setChartLibrary(CompressorChartLibrary.loadFromFile("BCL405B_charts.json"));
Compressor deposit / fouling degradation and washing
Model deposit (fouling) mass from process thermodynamics, its effect on
performance, where it lands per impeller, the degraded chart after N hours, and
online washing. Package neqsim.process.equipment.compressor. See the
Compressor Deposit and Performance Degradation
doc.
// 1) Deposit mass -> performance effect (combine several mechanisms)
CompressorDeposit dep = CompressorDeposit.fromCompressor(comp); // sizes foulable geometry
dep.addDeposit(DepositMechanism.SULFUR_S8, 1.2); // kg (S8 study)
dep.addDeposit(DepositMechanism.SALT_NACL, 0.4); // kg (salt study)
comp.setDepositModel(dep); // run() now degrades efficiency/power
comp.run();
double effLoss = 1.0 - dep.getEfficiencyMultiplier();
// 2) Deposit mass FROM the process (precipitation bridge)
SolidFlashDepositSource s8 =
new SolidFlashDepositSource(feed, "S8", DepositMechanism.SULFUR_S8, 0.3); // TPSolidflash
EntrainedSaltDepositSource salt =
new EntrainedSaltDepositSource(10.0, 0.05); // 10 kg/hr entrained water, 5 wt% salt
dep.accumulate(s8, 500.0); // deposit after 500 operating hours
dep.accumulate(salt, 500.0);
// 3) Degraded performance chart after N hours (chart-based machines)
CompressorChart chart500 = comp.buildDegradedChart();
// 4) Where deposits form (per impeller). Rigorous = real per-step flashed states:
comp.setPolytropicMethod("detailed");
comp.getPropertyProfile().setActive(true);
comp.run();
List<CompressorDepositProfile.StageDeposit> profile =
CompressorDepositProfile.computeFromPropertyProfile(comp, 5, "S8");
int worst = CompressorDepositProfile.worstStage(profile); // 1 = cold first impeller
// 5) Online washing: recommend fluid, plan rate, simulate removal
WashFluid fluid = CompressorDepositWash.recommend(dep); // salt->WATER, S8->XYLENE
CompressorDepositWash washer = new CompressorDepositWash();
washer.setContactEfficiency(0.7);
double rateKgHr = washer.requiredFluidRateKgHr(dep, fluid, 2.0, 3.0); // remove 2 kg in 3 h
CompressorDepositWash.WashResult r = comp.washOnline(fluid, rateKgHr, 3.0);
comp.run(); // performance recovers
Wash-fluid → deposit matching (screening solubilities): water dissolves salt/scale;
xylene/toluene dissolve S8 and wax; condensate dissolves wax; methanol moderate salt.
recommend() returns the fluid that removes the most mass — for mixed salt+S8 fouling,
wash in sequence (water, then xylene).
Cooler / Heater
Cooler cooler = new Cooler("Cooler", hotStream);
cooler.setOutTemperature(273.15 + 30.0);
Stream out = cooler.getOutletStream();
// After run: cooler.getDuty() — Watts
HeatExchanger (Two-Sided)
HeatExchanger has two feed/outlet sides indexed 0 and 1. Use
setFeedStream(int, StreamInterface) to connect both sides and
getOutStream(int) to retrieve the outlet for each side.
IMPORTANT: Do NOT use getOutletStream() when you need a specific
side — it only returns side 0. Always use getOutStream(int).
HeatExchanger hx = new HeatExchanger("E-100");
hx.setFeedStream(0, shellSideFeed); // side 0 = shell
hx.setFeedStream(1, tubeSideFeed); // side 1 = tube
// Optional: hx.setUAvalue(35000.0); // W/K
// After run: retrieve each side's outlet
Stream shellOut = (Stream) hx.getOutStream(0);
Stream tubeOut = (Stream) hx.getOutStream(1);
double duty = hx.getDuty(); // Watts
# Python
hx = HeatExchanger("E-100")
hx.setFeedStream(0, shell_feed)
hx.setFeedStream(1, tube_feed)
# Downstream connections:
cooler = Cooler("C-100", hx.getOutStream(int(0))) # shell side out
valve = ThrottlingValve("VLV-100", hx.getOutStream(int(1))) # tube side out
Valve (JT / Isenthalpic Expansion)
ThrottlingValve valve = new ThrottlingValve("JT Valve", stream);
valve.setOutletPressure(20.0);
Stream out = valve.getOutletStream();
CRITICAL: Always use ThrottlingValve inside a ProcessSystem for Joule-Thomson
cooling calculations. Manual PHflash() on a cloned fluid gives wrong JT temperatures
(tested: 14.9°C error vs 1.7°C with ThrottlingValve). The valve handles the isenthalpic
enthalpy bookkeeping internally.
# Python — Correct JT expansion pattern
proc = ProcessSystem()
feed = Stream('SG', fluid.clone())
feed.setFlowRate(flow, 'kg/hr')
feed.setTemperature(T_in, 'C')
feed.setPressure(P_in, 'bara')
proc.add(feed)
valve = ThrottlingValve('JT', feed)
valve.setOutletPressure(P_out)
proc.add(valve)
proc.run()
T_jt = float(valve.getOutletStream().getTemperature('C'))
Mixer
Mixer mixer = new Mixer("Mix");
mixer.addStream(stream1);
mixer.addStream(stream2);
Stream out = mixer.getOutletStream();
ComponentSplitter (TEG / Glycol Contactor — Water Removal)
Used to model TEG dehydration contactors as simple water-removal units.
Splits a stream per-component: splitFactor[k] = 1.0 keeps the component in
stream 0 (dry gas), 0.0 removes it to stream 1 (water).
TEG dehydration pattern: water is always the last component added,
so use [1.0] * (N-1) + [0.0] to remove only water.
// Java
ComponentSplitter dehydrator = new ComponentSplitter("TEG contactor", wetGasStream);
int nComp = wetGasStream.getFluid().getNumberOfComponents();
double[] sf = new double[nComp];
Arrays.fill(sf, 1.0);
sf[nComp - 1] = 0.0; // last component = water
dehydrator.setSplitFactors(sf);
// After run:
Stream dryGas = dehydrator.getSplitStream(0); // all components except water
Stream water = dehydrator.getSplitStream(1); // removed water
# Python
water_dehydration = neqsim.process.equipment.splitter.ComponentSplitter(
"dehyd", wet_gas_stream)
complen = wet_gas_stream.getFluid().getNumberOfComponents()
water_dehydration.setSplitFactors([1.0] * (complen - 1) + [0.0])
water_dehydration.run()
dry_gas = water_dehydration.getSplitStream(0)
When to use: Any absorber with a glycol-related name ("glyc", "teg", "dehydrat") should be modeled as a ComponentSplitter rather than a DistillationColumn. This avoids solver convergence issues and is the standard pattern for production platform models.
Pump
Pump pump = new Pump("P-100", liquidStream);
pump.setOutletPressure(20.0); // bara
pump.setIsentropicEfficiency(0.75); // 0-1
Stream out = pump.getOutletStream();
// After run: pump.getPower("kW")
Three operating modes:
- Isentropic (default): PS flash → isentropic enthalpy → divide by efficiency → PH flash
- Fixed outlet temperature:
pump.setOutletTemperature(40.0, "C")→ back-calculates power - Pump chart:
pump.getPumpChart()→ head, efficiency, NPSH curves
Pipeline
AdiabaticPipe pipe = new AdiabaticPipe("Pipeline", stream);
pipe.setLength(50000.0); // meters
pipe.setDiameter(0.508); // meters (20 inch)
Stream out = pipe.getOutletStream();
Route-Level Piping From STID/E3D Line Lists
For route pressure-drop tasks based on STID P&IDs, E3D exports, stress
isometrics, or line-list tables, prefer PipingRouteBuilder over manually
creating many pipe units. It creates a serial ProcessSystem with one
PipeBeggsAndBrills unit per segment and stores explicit material connection
metadata.
PipingRouteBuilder route = new PipingRouteBuilder()
.setDefaultPipeWallRoughness(45.0, "micrometer")
.setMinorLossFrictionFactor(0.02)
.addSegment("S1", "Manifold", "Valve Station", 100.0, "m", 0.2, "m")
.setSegmentWallThickness("S1", 8.0, "mm")
.addMinorLoss("S1", "manual valve", 1.0)
.addSegment("S2", "Valve Station", "Compressor Scrubber", 25.0, "m", 8.0, "inch")
.addMinorLoss("Valve Station->Compressor Scrubber", "long-radius bend", 0.3);
ProcessSystem routeProcess = route.build(feedStream);
routeProcess.run();
String routeJson = route.toJson();
To embed the extracted route in a larger flowsheet, add it to the existing
ProcessSystem and use the returned outlet stream as the inlet to downstream
equipment:
ProcessSystem process = new ProcessSystem("Full plant process");
process.add(feedStream);
StreamInterface routeOutlet = route.addToProcessSystem(process, feedStream);
Cooler downstreamCooler = new Cooler("Downstream cooler", routeOutlet);
process.add(downstreamCooler);
process.run();
If the route starts from an upstream equipment outlet, use the overload with
source-equipment metadata: route.addToProcessSystem(process, sep.getGasOutStream(), "HP Sep", "gasOut").
Always preserve source document/page/row references in the task notes and export
route.toJson() in the task results so later STID work can reuse the route.
Recycle (Detailed)
Recycles enable iterative convergence of process loops. The ProcessSystem
automatically detects and iterates recycles up to 100 times.
// 1. Create placeholder stream with estimated conditions
Stream placeholder = new Stream("recycle estimate", fluidGuess.clone());
placeholder.setFlowRate(estimatedFlow, "kg/hr");
placeholder.setTemperature(estimatedT, "C");
placeholder.setPressure(estimatedP, "bara");
process.add(placeholder);
// 2. Build downstream equipment using the placeholder as input
Mixer mixer = new Mixer("recycle mixer");
mixer.addStream(mainFeed);
mixer.addStream(placeholder); // ← placeholder used here
process.add(mixer);
// ... more equipment in the loop ...
// 3. Create Recycle that connects actual outlet back to placeholder
Recycle recycle = new Recycle("RCY-1");
recycle.addStream(actualOutletStream); // downstream end of loop
recycle.setOutletStream(placeholder); // connects back to start
recycle.setTolerance(1e-3); // tighter than default 1e-2
process.add(recycle);
Convergence tuning:
recycle.setFlowTolerance(1e-3); // flow convergence (default 1e-2)
recycle.setTemperatureTolerance(1e-3); // temperature convergence
recycle.setCompositionTolerance(1e-3); // composition convergence
recycle.setPriority(50); // lower = solved first (default 100)
recycle.setAccelerationMethod("Wegstein"); // or "Direct Substitution", "Broyden"
Priority-based nesting: Set lower priority numbers on inner recycle loops.
The RecycleController solves lower-priority recycles first, then higher.
ProcessSystem hard cap: 100 iterations (not user-configurable).
Adjuster
Adjuster adjuster = new Adjuster("Adj");
adjuster.setAdjustedVariable(equipment, "methodName");
adjuster.setTargetVariable(stream, "methodName", targetValue);
ProcessSystem Assembly
ProcessSystem process = new ProcessSystem();
process.add(feed);
process.add(separator);
process.add(compressor);
process.add(cooler);
process.run(); // Run ONCE after adding all equipment
For multi-area plants, use ProcessModel to combine multiple ProcessSystem instances (see below).
ProcessModel — Combining Multiple Process Areas (MANDATORY for Large Plants)
For large process plants (platforms, refineries, gas plants), split the model into
separate ProcessSystem objects per process area, then combine them into a single
ProcessModel. NEVER try to add a ProcessModule or ProcessSystem to another
ProcessSystem — use ProcessModel as the top-level container.
Architecture Pattern (from reference platform models)
ProcessModel ("Gas Platform") ← TOP-LEVEL CONTAINER
├── ProcessSystem ("well process") ← Well feed & manifold
├── ProcessSystem ("separation train A") ← HP/LP separation
├── ProcessSystem ("separation train B") ← HP/LP separation
├── ProcessSystem ("TEX process A") ← Turbo-expander
├── ProcessSystem ("TEX process B") ← Turbo-expander
├── ProcessSystem ("export compressor A") ← Gas compression
├── ProcessSystem ("export gas") ← Gas export pipeline
└── ProcessSystem ("export oil") ← Oil export
Java Example
// Each area is its own ProcessSystem
ProcessSystem wellProcess = new ProcessSystem();
wellProcess.add(wellFeed);
wellProcess.add(manifold);
wellProcess.add(splitter);
ProcessSystem separationA = new ProcessSystem();
separationA.add(new Heater("HP heater", splitter.getSplitStream(0)));
separationA.add(new ThreePhaseSeparator("1st stage", ...));
// ... more equipment
ProcessSystem compressionA = new ProcessSystem();
compressionA.add(new Compressor("export comp",
separationA.getUnit("gas mixer").getOutletStream())); // cross-ref
// Combine into ProcessModel
ProcessModel plant = new ProcessModel();
plant.add("well process", wellProcess);
plant.add("separation train A", separationA);
plant.add("export compressor A", compressionA);
plant.run(); // Iterates until all converge
// Access equipment by process area
plant.get("separation train A").getUnit("1st stage separator");
// Convergence info
System.out.println(plant.getConvergenceSummary());
System.out.println(plant.getMassBalanceReport());
Python Example (Recommended Pattern)
The reference model uses functions that return ProcessSystem objects:
def create_well_feed_model(inp):
well_process = neqsim.process.processmodel.ProcessSystem()
feed = Stream("feed", fluid)
feed.setFlowRate(inp.flow_rate, "kg/hr")
well_process.add(feed)
splitter = Splitter("manifold", feed)
splitter.setSplitFactors([0.5, 0.5])
well_process.add(splitter)
return well_process
def create_separation_process(inp, feed_stream):
sep_process = neqsim.process.processmodel.ProcessSystem()
separator = ThreePhaseSeparator("1st stage", feed_stream) # cross-ref!
sep_process.add(separator)
# ... more equipment
return sep_process
# Build and run each area
well_model = create_well_feed_model(params)
well_model.run()
sep_train_A = create_separation_process(params,
well_model.getUnit("manifold").getSplitStream(0)) # cross-system stream
sep_train_A.run()
# Combine into ProcessModel
ProcessModel = jneqsim.process.processmodel.ProcessModel
plant = ProcessModel()
plant.add("well process", well_model)
plant.add("separation train A", sep_train_A)
plant.run() # Iterates until convergence
print(plant.getConvergenceSummary())
print(plant.getMassBalanceReport())
ProcessModel Key Features
| Feature | Method |
|---|---|
| Add named sub-process | add("name", processSystem) |
| Get sub-process | get("name") |
| Remove sub-process | remove("name") |
| Run all (iterates to convergence) | run() |
| Run single step | runStep() |
| Run in background thread | runAsTask() returns Future |
| Check convergence | isModelConverged(), getConvergenceSummary() |
| Mass balance report | getMassBalanceReport(), getFailedMassBalanceReport() |
| Validation | validateSetup(), validateAll(), getValidationReport() |
| Execution analysis | getExecutionPartitionInfo() |
| Set convergence tolerance | setTolerance(1e-4) or individual setFlowTolerance() etc. |
| Save/load model | saveToNeqsim("file.neqsim"), loadFromNeqsim("file.neqsim") |
| JSON report | getReport_json() |
| Automation facade | getAutomation() returns ProcessAutomation (string-addressable variables) |
| Lifecycle state | ProcessModelState.fromProcessModel(plant), .saveToFile(), .compare(v1, v2) |
Cross-System Stream Sharing
Streams cross sub-system boundaries by direct object reference:
- Equipment in System B takes an outlet stream from System A as a constructor argument
ProcessModel.run()executes systems in insertion order- System A populates its outlet streams BEFORE System B reads from them
- Order of
add()calls matters — add upstream systems first
ProcessModel vs ProcessModule vs ProcessSystem
| Class | Purpose | Use When |
|---|---|---|
ProcessSystem |
Single process area with equipment | Always — the basic building block |
ProcessModel |
Named collection of ProcessSystems with convergence tracking | Multi-area plants (platforms, gas plants) |
ProcessModule |
Legacy container for ProcessSystems | Backward compatibility only — prefer ProcessModel |
NEVER add a ProcessModule or ProcessModel to a ProcessSystem — it will throw TypeError.
Key Rules
- Clone fluids before branching:
fluid.clone()to avoid shared-state bugs - Equipment constructors take
(String name, StreamInterface inlet) - Connect equipment via outlet streams — don't create separate streams
- Add equipment to
ProcessSystemin topological order - Call
process.run()only ONCE after building the entire flowsheet - For multi-area plants: use
ProcessModelto combineProcessSystemobjects — never nest them
Automation API (String-Addressable Variables)
Use ProcessAutomation for agent-friendly variable access — no Java class navigation needed.
Setup and Discovery
ProcessAutomation auto = process.getAutomation(); // or plant.getAutomation()
List<String> units = auto.getUnitList(); // ["Feed Gas", "HP Sep", ...]
List<SimulationVariable> vars = auto.getVariableList("HP Sep");
// Each variable: address, name, type (INPUT/OUTPUT), defaultUnit, description
String eqType = auto.getEquipmentType("HP Sep"); // "Separator"
Read / Write Variables
// Read with unit conversion (dot-notation addressing)
double temp = auto.getVariableValue("HP Sep.gasOutStream.temperature", "C");
double flow = auto.getVariableValue("HP Sep.gasOutStream.flowRate", "kg/hr");
// Write INPUT variables, then re-run
auto.setVariableValue("Compressor.outletPressure", 150.0, "bara");
process.run();
Multi-Area Addressing
ProcessAutomation plantAuto = plant.getAutomation();
List<String> areas = plantAuto.getAreaList();
// Area-qualified: "Area::Unit.property"
double t = plantAuto.getVariableValue("Separation::HP Sep.gasOutStream.temperature", "C");
Lifecycle State (Save / Restore / Compare)
JSON snapshots for reproducibility and version tracking.
// Save
ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);
state.setName("Gas Processing"); state.setVersion("1.0.0");
state.saveToFile("model_v1.json");
// Load and validate
ProcessSystemState loaded = ProcessSystemState.loadFromFile("model_v1.json");
assert loaded.validate().isValid();
// Multi-area
ProcessM
…(truncated)