| name | neqsim-api-patterns |
| description | NeqSim API patterns and code recipes. USE WHEN: writing Java or Python code that uses NeqSim for thermodynamic calculations, process simulation, or property retrieval. Covers EOS selection, fluid creation, flash calculations, property access, equipment patterns, and unit conventions. |
| last_verified | 2026-07-10 |
NeqSim API Patterns
Copy-paste reference for common NeqSim operations. All Java code must be Java 8 compatible.
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)
SystemInterface fluid = new SystemSrkEos(273.15 + 25.0, 60.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");
fluid.setMultiPhaseCheck(true);
Oil Characterization (C7+ Fractions)
fluid.addTBPfraction("C7", 0.05, 92.0 / 1000, 0.727);
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:
SystemInterface fluid = EclipseFluidReadWrite.read("path/to/fluid.e300");
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 after init(0))
BICS — surface-condition BICs (parsed, same lower-triangular format as BIC)
SSHIFTS — surface-condition volume shift
PEDERSEN — activates Pedersen viscosity model
EOS keyword determines fluid class:
EOS\nSRK / → SystemSrkEos
EOS\nPR /\nPRCORR → SystemPrEos1978
EOS\nPR /\nPRLKCORR → SystemPrLeeKeslerEos ← use for PR-LK matching
EOS\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.).
from neqsim import jneqsim
EclipseFluidReadWrite = jneqsim.thermo.util.readwrite.EclipseFluidReadWrite
fluid = EclipseFluidReadWrite.read("path/to/fluid.e300")
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();
fluid.initProperties();
double density = fluid.getDensity("kg/m3");
double molarMass = fluid.getMolarMass("kg/mol");
double Z = fluid.getZ();
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");
int numPhases = fluid.getNumberOfPhases();
boolean hasGas = fluid.hasPhaseType("gas");
Other Flash Types
ops.PHflash(enthalpy);
ops.PSflash(entropy);
ops.dewPointTemperatureFlash();
ops.bubblePointPressureFlash();
ops.hydrateFormationTemperature();
ops.calcPTphaseEnvelope();
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 |
Process Equipment Patterns
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.
sep.initMechanicalDesign();
SeparatorMechanicalDesign design =
(SeparatorMechanicalDesign) sep.getMechanicalDesign();
design.setMaxOperationPressure(85.0);
design.setMaxOperationTemperature(273.15 + 80);
design.setGasLoadFactor(0.107);
design.setRetentionTime(120.0);
design.setFg(0.5);
design.setInletNozzleID(0.254);
design.setGasOutletNozzleID(0.20);
design.setOilOutletNozzleID(0.15);
design.setDemisterType("wire_mesh");
design.setDemisterPressureDrop(1.5);
design.setDemisterThickness(150.0);
design.setFoamAllowanceFactor(1.0);
design.setInletPipeDiameter(0.254);
design.setInletDeviceType(InletDeviceModel.InletDeviceType.INLET_VANE);
design.setGasLiquidSurfaceTension(0.020);
design.addSeparatorSection("Demister", "meshpad");
design.setWeirHeightAbsolute(0.30);
design.setWeirLength(1.5);
design.setBootVolume(2.0);
design.setMistEliminatorDpCoeff(150.0);
design.setMistEliminatorThickness(0.15);
design.readDesignSpecifications();
design.calcDesign();
String report = design.toJson();
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.
ThreePhaseSeparator sep = new ThreePhaseSeparator("1st Stage", feed);
sep.setEntrainment(0.001, "mole", "feed", "oil", "gas");
sep.setEntrainment(0.001, "mole", "feed", "aqueous", "gas");
sep.setEntrainment(0.005, "mass", "product", "aqueous", "oil");
sep.setEntrainment(500e-6, "mass", "product", "oil", "aqueous");
sep.run();
- Base
Separator supports 3 paths: oil→gas, aqueous→gas, gas→liquid.
ThreePhaseSeparator supports all 6 paths: oil→gas, aqueous→gas,
gas→oil, gas→aqueous, oil→aqueous, aqueous→oil.
- With
specifiedStream="product", val is clamped: ≤0 transfers nothing,
≥1 transfers 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();
SeparatorMechanicalDesign design =
(SeparatorMechanicalDesign) sep.getMechanicalDesign();
design.calcDesign();
design.setDesign();
design.setDemisterType("wire_mesh");
design.setDemisterSubType("High Efficiency");
SeparatorEfficiencyReport report = design.calculateSeparationEfficiency();
double opK = report.getOperatingKFactor();
double effGL = report.getOverallGasLiquidEfficiency();
String verdict = report.getVerdict();
for (InternalOperatingWindow w : report.getWindows()) {
}
String json = report.toJson();
design.setEfficiencyModelEnabled(true);
sep.run();
design.setEfficiencyModelEnabled(false);
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);
Stream out = comp.getOutletStream();
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");
comp.run();
List<String> charts = comp.getAvailableCharts();
String active = comp.getSelectedChartName();
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.
CompressorDeposit dep = CompressorDeposit.fromCompressor(comp);
dep.addDeposit(DepositMechanism.SULFUR_S8, 1.2);
dep.addDeposit(DepositMechanism.SALT_NACL, 0.4);
comp.setDepositModel(dep);
comp.run();
double effLoss = 1.0 - dep.getEfficiencyMultiplier();
SolidFlashDepositSource s8 =
new SolidFlashDepositSource(feed, "S8", DepositMechanism.SULFUR_S8, 0.3);
EntrainedSaltDepositSource salt =
new EntrainedSaltDepositSource(10.0, 0.05);
dep.accumulate(s8, 500.0);
dep.accumulate(salt, 500.0);
CompressorChart chart500 = comp.buildDegradedChart();
comp.setPolytropicMethod("detailed");
comp.getPropertyProfile().setActive(true);
comp.run();
List<CompressorDepositProfile.StageDeposit> profile =
CompressorDepositProfile.computeFromPropertyProfile(comp, 5, "S8");
int worst = CompressorDepositProfile.worstStage(profile);
WashFluid fluid = CompressorDepositWash.recommend(dep);
CompressorDepositWash washer = new CompressorDepositWash();
washer.setContactEfficiency(0.7);
double rateKgHr = washer.requiredFluidRateKgHr(dep, fluid, 2.0, 3.0);
CompressorDepositWash.WashResult r = comp.washOnline(fluid, rateKgHr, 3.0);
comp.run();
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();
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);
hx.setFeedStream(1, tubeSideFeed);
Stream shellOut = (Stream) hx.getOutStream(0);
Stream tubeOut = (Stream) hx.getOutStream(1);
double duty = hx.getDuty();
hx = HeatExchanger("E-100")
hx.setFeedStream(0, shell_feed)
hx.setFeedStream(1, tube_feed)
cooler = Cooler("C-100", hx.getOutStream(int(0)))
valve = ThrottlingValve("VLV-100", hx.getOutStream(int(1)))
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.
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.
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;
dehydrator.setSplitFactors(sf);
Stream dryGas = dehydrator.getSplitStream(0);
Stream water = dehydrator.getSplitStream(1);
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);
pump.setIsentropicEfficiency(0.75);
Stream out = pump.getOutletStream();
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);
pipe.setDiameter(0.508);
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.
Stream placeholder = new Stream("recycle estimate", fluidGuess.clone());
placeholder.setFlowRate(estimatedFlow, "kg/hr");
placeholder.setTemperature(estimatedT, "C");
placeholder.setPressure(estimatedP, "bara");
process.add(placeholder);
Mixer mixer = new Mixer("recycle mixer");
mixer.addStream(mainFeed);
mixer.addStream(placeholder);
process.add(mixer);
Recycle recycle = new Recycle("RCY-1");
recycle.addStream(actualOutletStream);
recycle.setOutletStream(placeholder);
recycle.setTolerance(1e-3);
process.add(recycle);
Convergence tuning:
recycle.setFlowTolerance(1e-3);
recycle.setTemperatureTolerance(1e-3);
recycle.setCompositionTolerance(1e-3);
recycle.setPriority(50);
recycle.setAccelerationMethod("Wegstein");
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();
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
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", ...));
ProcessSystem compressionA = new ProcessSystem();
compressionA.add(new Compressor("export comp",
separationA.getUnit("gas mixer").getOutletStream()));
ProcessModel plant = new ProcessModel();
plant.add("well process", wellProcess);
plant.add("separation train A", separationA);
plant.add("export compressor A", compressionA);
plant.run();
plant.get("separation train A").getUnit("1st stage separator");
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)
sep_process.add(separator)
return sep_process
well_model = create_well_feed_model(params)
well_model.run()
sep_train_A = create_separation_process(params,
well_model.getUnit("manifold").getSplitStream(0))
sep_train_A.run()
ProcessModel = jneqsim.process.processmodel.ProcessModel
plant = ProcessModel()
plant.add("well process", well_model)
plant.add("separation train A", sep_train_A)
plant.run()
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
ProcessSystem in topological order
- Call
process.run() only ONCE after building the entire flowsheet
- For multi-area plants: use
ProcessModel to combine ProcessSystem objects — 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();
List<String> units = auto.getUnitList();
List<SimulationVariable> vars = auto.getVariableList("HP Sep");
String eqType = auto.getEquipmentType("HP Sep");
Read / Write Variables
double temp = auto.getVariableValue("HP Sep.gasOutStream.temperature", "C");
double flow = auto.getVariableValue("HP Sep.gasOutStream.flowRate", "kg/hr");
auto.setVariableValue("Compressor.outletPressure", 150.0, "bara");
process.run();
Multi-Area Addressing
ProcessAutomation plantAuto = plant.getAutomation();
List<String> areas = plantAuto.getAreaList();
double t = plantAuto.getVariableValue("Separation::HP Sep.gasOutStream.temperature", "C");
Lifecycle State (Save / Restore / Compare)
JSON snapshots for reproducibility and version tracking.
ProcessSystemState state = ProcessSystemState.fromProcessSystem(process);
state.setName("Gas Processing"); state.setVersion("1.0.0");
state.saveToFile("model_v1.json");
ProcessSystemState loaded = ProcessSystemState.loadFromFile("model_v1.json");
assert loaded.validate().isValid();
ProcessModelState ms = ProcessModelState.fromProcessModel(plant);
ms.saveToFile("plant_v1.json");
ProcessModelState.ModelDiff diff = ProcessModelState.compare(v1, v2);
byte[] bytes = ms.toCompressedBytes();
ProcessModelState restored = ProcessModelState.fromCompressedBytes(bytes);
Design Feasibility Reports
After running equipment in a process simulation, generate a feasibility report to answer:
"Is this machine realistic to build? What will it cost? Who can supply it?"
Compressor Feasibility
CompressorDesignFeasibilityReport report =
new CompressorDesignFeasibilityReport(compressor);
report.setDriverType("gas-turbine");
report.setCompressorType("centrifugal");
report.setAnnualOperatingHours(8000);
report.generateReport();
String verdict = report.getVerdict();
String json = report.toJson();
List<SupplierMatch> suppliers = report.getMatchingSuppliers();
report.applyChartToCompressor();
Heat Exchanger / Cooler / Heater Feasibility
HeatExchangerDesignFeasibilityReport hxReport =
new HeatExchangerDesignFeasibilityReport(heatExchanger);
hxReport.setExchangerType("shell-and-tube");
hxReport.setDesignStandard("TEMA-R");
hxReport.setAnnualOperatingHours(8000);
hxReport.generateReport();
String verdict = hxReport.getVerdict();
String json = hxReport.toJson();
List<HXSupplierMatch> suppliers = hxReport.getMatchingSuppliers();
Key points:
- Equipment must have been
run() before generating the report
- Verdicts:
FEASIBLE, FEASIBLE_WITH_WARNINGS, NOT_FEASIBLE
- Issues have severity:
BLOCKER (not feasible), WARNING (review), INFO (note)
- Supplier matching uses built-in OEM databases (
CompressorSuppliers.csv, HeatExchangerSuppliers.csv)
- Reports include: operating point, mechanical design, cost estimation, supplier list, issues
- For compressors: also generates performance curves from templates
When to run feasibility checks:
- Any task involving equipment sizing or selection
- Process design tasks where cost or buildability matters
- Field development or FEED-level studies
- When the user asks "is this realistic?", "can this be built?", "what will it cost?"
Heat Exchanger Thermal-Hydraulic Design
TEMA-level shell-and-tube thermal design with tube/shell-side HTCs, pressure drops,
LMTD correction, vibration screening, and full mechanical design.
Standalone Thermal Calculation
ThermalDesignCalculator calc = new ThermalDesignCalculator();
calc.setTubeODm(0.01905);
calc.setTubeIDm(0.01483);
calc.setTubeLengthm(6.0);
calc.setTubeCount(200);
calc.setTubePasses(2);
calc.setTubePitchm(0.0254);
calc.setTriangularPitch(true);
calc.setShellIDm(0.489);
calc.setBaffleSpacingm(0.15);
calc.setBaffleCount(30);
calc.setBaffleCut(0.25);
calc.setTubeSideFluid(995.0, 0.0008, 4180.0, 0.62, 5.0, true);
calc.setShellSideFluid(820.0, 0.003, 2200.0, 0.13, 8.0);
calc.setShellSideMethod(ThermalDesignCalculator.ShellSideMethod.BELL_DELAWARE);
calc.calculate();
String json = calc.toJson();
LMTD Correction Factor
double ft = LMTDcorrectionFactor.calcFt(tHotIn, tHotOut, tColdIn, tColdOut, 1);
int minShells = LMTDcorrectionFactor.requiredShellPasses(tHotIn, tHotOut, tColdIn, tColdOut);
Vibration Screening
VibrationAnalysis.VibrationResult vib = VibrationAnalysis.performScreening(
tubeOD, tubeID, unsupportedSpan, tubeMaterialE, tubeDensity,
fluidDensityTube, fluidDensityShell, "fixed-fixed",
crossflowVelocity, tubePitch, true, shellID, sonicVelocity);
if (!vib.passed) {
}
Full Shell-and-Tube Mechanical + Thermal Design
ShellAndTubeDesignCalculator stCalc = new ShellAndTubeDesignCalculator();
stCalc.setTemaDesignation("AES");
stCalc.setTemaClass(TEMAClass.R);
stCalc.setRequiredArea(50.0);
stCalc.setShellSidePressure(30.0);
stCalc.setTubeSidePressure(10.0);
stCalc.setDesignTemperature(200.0);
stCalc.setShellMaterialGrade("SA-516-70");
stCalc.setTubeMaterialGrade("SA-179");
stCalc.setSourServiceAssessment(true);
stCalc.setH2sPartialPressure(0.01);
stCalc.setTubeSideFluidProperties(995.0, 0.0008, 4180.0, 0.62, 5.0, true);
stCalc.setShellSideFluidProperties(820.0, 0.003, 2200.0, 0.13, 8.0);
stCalc.setShellSideMethod(ThermalDesignCalculator.ShellSideMethod.BELL_DELAWARE);
stCalc.calculate();
String json = stCalc.toJson();
Standards: TEMA R/C/B, ASME VIII Div.1 (UHX-13, UG-27, UG-37, UG-99),
NACE MR0175/ISO 15156, Bell-Delaware, Gnielinski, Von Karman, Connors criterion.
CO2 Injection Well Analysis
Full-stack safety analysis for CO2 injection wells covering steady-state flow,
phase boundary mapping, impurity enrichment, shutdown transients, and flow corrections.
CO2InjectionWellAnalyzer (High-Level Orchestrator)
CO2InjectionWellAnalyzer analyzer = new CO2InjectionWellAnalyzer("InjectionWell-1");
analyzer.setFluid(co2Fluid);
analyzer.setWellGeometry(1300.0, 0.1571, 5e-5);
analyzer.setOperatingConditions(90.0, 25.0, 150000.0);
analyzer.setFormationTemperature(4.0, 43.0);
analyzer.addTrackedComponent("hydrogen", 0.10);
analyzer.addTrackedComponent("nitrogen", 0.05);
analyzer.runFullAnalysis();
boolean safe = analyzer.isSafeToOperate();
Map<String, Object> results = analyzer.getResults();
ImpurityMonitor (Measurement Device)
ImpurityMonitor monitor = new ImpurityMonitor("H2-Monitor", stream);
monitor.addTrackedComponent("hydrogen", 0.10);
monitor.setPrimaryComponent("hydrogen");
double gasH2 = monitor.getGasPhaseMoleFraction("hydrogen");
double enrichment = monitor.getEnrichmentFactor("hydrogen");
boolean alarm = monitor.isAlarmExceeded("hydrogen");
Map<String, Map<String, Double>> report = monitor.getFullReport();
TransientWellbore (Shutdown Cooling)
TransientWellbore wellbore = new TransientWellbore("Shutdown", stream);
wellbore.setWellDepth(1300.0);
wellbore.setTubingDiameter(0.1571);
wellbore.setFormationTemperature(273.15 + 4.0, 273.15 + 43.0);
wellbore.setShutdownCoolingRate(6.0);
wellbore.setNumberOfSegments(10);
wellbore.runShutdownSimulation(48.0, 1.0);
List<TransientSnapshot> snaps = wellbore.getSnapshots();
double maxH2 = wellbore.getMaxGasPhaseConcentration("hydrogen");
PipeBeggsAndBrills (Formation Temperature Gradient)
PipeBeggsAndBrills pipe = new PipeBeggsAndBrills("Wellbore", feed);
pipe.setLength(1300.0);
pipe.setElevation(-1300.0);
pipe.setDiameter(0.1571);
pipe.setFormationTemperatureGradient(4.0, -0.03, "C");
pipe.run();
CO2FlowCorrections (Static Utility)
boolean co2Dominant = CO2FlowCorrections.isCO2DominatedFluid(system);
double holdupCorr = CO2FlowCorrections.getLiquidHoldupCorrectionFactor(system);
double frictionCorr = CO2FlowCorrections.getFrictionCorrectionFactor(system);
boolean dense = CO2FlowCorrections.isDensePhase(system);
double Tr = CO2FlowCorrections.getReducedTemperature(system);
Engineering Deliverables
Generate study-class-appropriate engineering documents from a converged ProcessSystem.
StudyClass and Package
EngineeringDeliverablesPackage pkg =
new EngineeringDeliverablesPackage(process, StudyClass.CLASS_A);
pkg.generate();
String json = pkg.toJson();
orchestrator.setStudyClass(StudyClass.CLASS_A);
orchestrator.runCompleteDesignWorkflow();
EngineeringDeliverablesPackage pkg = orchestrator.getEngineeringDeliverables();
| Study Class | Deliverables |
|---|
| CLASS_A (FEED/Detail) | PFD, Thermal Utilities, Alarm/Trip, Spare Parts, Fire Scenarios, Noise, Instrument Schedule |
| CLASS_B (Concept/Pre-FEED) | PFD, Thermal Utilities, Fire Scenarios, Instrument Schedule |
| CLASS_C (Screening) | PFD only |
Instrument Schedule Generator (with Live Device Bridge)
Creates ISA-5.1 tagged instruments and optionally registers real MeasurementDeviceInterface
objects on the ProcessSystem for dynamic simulation:
InstrumentScheduleGenerator instrGen = new InstrumentScheduleGenerator(process);
instrGen.setRegisterOnProcess(true);
instrGen.generate();
List<InstrumentScheduleGenerator.InstrumentEntry> all = instrGen.getEntries();
List<InstrumentScheduleGenerator.InstrumentEntry> pts =
instrGen.getEntriesByType(InstrumentScheduleGenerator.MeasuredVariable.PRESSURE);
for (InstrumentScheduleGenerator.InstrumentEntry e : all) {
System.out.println(e.getTag() + " " + e.getEquipmentName()
+ " SIL=" + e.getSilRating());
if (e.getLiveDevice() != null) {
System.out.println(" Live: " + e.getLiveDevice().getMeasuredValue());
}
}
String instrJson = instrGen.toJson();
Tag numbering convention: PT-100+, TT-200+, LT-300+, FT-400+ (ISA-5.1).
Phase Envelope Calculation and Interpretation
For phase-envelope generation, plotting, physical branch classification, zero/trace-component
handling, or Michelsen solver changes, load neqsim-phase-envelope. This section is the compact
API reference; the dedicated skill owns the end-to-end workflow and regression rules.
Calculating a PT Phase Envelope
SystemInterface fluid = new SystemSrkEos(273.15 + 25.0, 50.0);
fluid.addComponent("methane", 0.85);
fluid.addComponent("ethane", 0.10);
fluid.addComponent("propane", 0.05);
fluid.setMixingRule("classic");
ThermodynamicOperations ops = new ThermodynamicOperations(fluid);
ops.calcPTphaseEnvelope(true, 1.0);
PTPhaseEnvelopeMichelsen envelope = (PTPhaseEnvelopeMichelsen) ops.getOperation();
double[] cricondenBar = envelope.getCricondenBar();
double[] cricondenTherm = envelope.getCricondenTherm();
double critT = envelope.getCriticalTemperature();
double critP = envelope.getCriticalPressure();
CRITICAL: Branch Classification Bug with bubblePointFirst=true
When using calcPTphaseEnvelope(true, 1.0) (bubblePointFirst=true), the NeqSim
Michelsen algorithm stores the envelope branches with SWAPPED labels:
getBubblePointTemperatures() / getBubblePointPressures() → actually the DEW curve (right side, higher T, includes cricondentherm)
getDewPointTemperatures() / getDewPointPressures() → actually the BUBBLE curve (left side, lower T)
Root cause: The algorithm initializes isDewPhase=true regardless of the
bubblePointFirst flag. When starting from the bubble side, initial points go
into the dew list. At the critical point, isDewPhase flips, sending post-CP
points (the actual dew side) into the bubble list.
Always determine which branch is which using physical reasoning:
branch_A_T = np.array(envelope.getBubblePointTemperatures())
branch_A_P = np.array(envelope.getBubblePointPressures())
branch_B_T = np.array(envelope.getDewPointTemperatures())
branch_B_P = np.array(envelope.getDewPointPressures())
if branch_A_T.max() > branch_B_T.max():
dew_T, dew_P = branch_A_T, branch_A_P
bub_T, bub_P = branch_B_T, branch_B_P
else:
dew_T, dew_P = branch_B_T, branch_B_P
bub_T, bub_P = branch_A_T, branch_A_P
Phase Envelope Physical Interpretation
Bubble point curve (left side of envelope):
- Boundary between subcooled liquid and two-phase region
- At the bubble point, the first infinitesimal bubble of vapor forms
- Crossing from left to right: liquid → two-phase
Dew point curve (right side of envelope):
- Boundary between superheated vapor and two-phase region
- At the dew point, the first infinitesimal drop of liquid forms
- Crossing from right to left: vapor → two-phase
Key points on the envelope:
- Critical point: Where bubble and dew curves meet; liquid and vapor become indistinguishable
- Cricondenbar: Maximum pressure on the envelope (above this, no two-phase region exists at any T)
- Cricondentherm: Maximum temperature on the envelope (above this, no liquid forms at any P); always on the DEW curve side
- For lean gas (mostly methane): cricondenbar and cricondentherm are both on the dew curve side, close to the critical point
- For rich gas/condensate: the envelope is wider; cricondentherm extends to significantly higher temperatures
Retrograde condensation region (between cricondenbar and cricondentherm on the dew curve):
- Reducing pressure at constant T causes MORE liquid to form (counter-intuitive)
- This is critical for gas condensate reservoirs and pipeline design
Documentation Code Verification
When writing code examples for documentation (markdown guides, cookbook recipes, tutorials):
- Read the source class first — verify every method signature, constructor, inner class
- Write a JUnit test that calls every documented API method (append to
DocExamplesCompilationTest.java)
- Run the test and confirm it passes before publishing the doc
- Common pitfalls:
- Plus fraction names: use
"C20" not "C20+" (the + character breaks parsing)
- Set mixing rule BEFORE calling
characterisePlusFraction()
getUnit("name") not getUnitOperation("name")
setDepreciationYears takes double, not int
- Risk thresholds: always read source for actual comparison logic (subcooling direction, enum ordering)