Source profileQuality 96/100

equinor/neqsim/.github/skills/neqsim-process-extraction/SKILL.md

neqsim-process-extraction

Extracts process simulation data from unstructured sources (text, tables, PFDs, data sheets, STID/E3D line lists) and converts it to NeqSim JSON builder format or PipingRouteBuilder route models. USE WHEN: a user provides a process description, PFD, operating data, line-list table, or design document and wants a running NeqSim simulation. Covers equipment mapping, stream wiring, route hydraulics, unit conversion, composition normalization, and confidence scoring.

Source repository stars
147
Declared platforms
0
Static risk flags
1
Last source update
2026-08-28
Source checked
2026-08-28

Decision brief

What it does: where it fits

Convert unstructured engineering information into the canonical NeqSim JSON format accepted by ProcessSystem.fromJson() and ProcessSystem.fromJsonAndRun().

Best for

  • USE WHEN: a user provides a process description, PFD, operating data, line-list table, or design document and wants a running NeqSim simulation.

Not for

  • Component Name Mapping (CRITICAL)
  • CPA Mixing Rule Name (CRITICAL)

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/equinor/neqsim --skill ".github/skills/neqsim-process-extraction"
Safe inspection promptEditorial

Inspect the Agent Skill "neqsim-process-extraction" from https://github.com/equinor/neqsim/blob/9e4e36d4b6a59404ac9aa629740fbc312610d3c8/.github/skills/neqsim-process-extraction/SKILL.md at commit 9e4e36d4b6a59404ac9aa629740fbc312610d3c8. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

Workflow

What the source asks the agent to do

  1. 01

    8. Extraction Workflow

    Follow this step-by-step process for every extraction:

    Text description — paragraph or bullet list describing a processTable / spreadsheet — heat & mass balance, operating data, well testPFD / sketch — process flow diagram (described or as image)
  2. 02

    Step 1: Identify the Source Type

    Text description — paragraph or bullet list describing a process

    Text description — paragraph or bullet list describing a processTable / spreadsheet — heat & mass balance, operating data, well testPFD / sketch — process flow diagram (described or as image)
  3. 03

    Step 2: Extract Fluid Composition

    1. Look for: mole fractions, mol%, weight%, component tables 2. Map component names to NeqSim names using the Component Name Mapping table 3. Normalize to mole fractions summing to 1.0 4. If weight% given, note it as an assumption (NeqSim uses mole fractions) 5. If no compositio…

    Look for: mole fractions, mol%, weight%, component tablesMap component names to NeqSim names using the Component Name Mapping tableNormalize to mole fractions summing to 1.0
  4. 04

    Step 3: Extract Equipment List

    1. Scan for equipment keywords using the Equipment Type Mapping table 2. Match the longest keyword first (e.g., "three-phase separator" before "separator") 3. Assign unique names/tags (use P&ID tags if provided, or generate descriptive names) 4. Record the NeqSim type for each

    Scan for equipment keywords using the Equipment Type Mapping tableMatch the longest keyword first (e.g., "three-phase separator" before "separator")Assign unique names/tags (use P&ID tags if provided, or generate descriptive names)
  5. 05

    Step 4: Extract Stream Connectivity

    1. Look for phrases indicating flow direction: "enters", "goes to", "feeds", "is routed to", "flows to", "passes through" 2. Identify which phase exits which equipment: "gas from the separator", "oil from the 3-phase separator", "compressed gas" 3. Build dot-notation references:…

    Look for phrases indicating flow direction: "enters", "goes to", "feeds", "is routed to", "flows to", "passes through"Identify which phase exits which equipment: "gas from the separator", "oil from the 3-phase separator", "compressed gas"Build dot-notation references: "equipmentname.port"

Permission review

Static risk signals and limitations

Writes files

medium · line 1813

The documentation asks the agent to create, modify, or delete local files.

Save to a `.json` file for portability and version control

Evidence record

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score96/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars147SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
equinor/neqsim
Skill path
.github/skills/neqsim-process-extraction/SKILL.md
Commit
9e4e36d4b6a59404ac9aa629740fbc312610d3c8
License
Apache-2.0
Collected
2026-08-28
Default branch
master
View the original SKILL.md

NeqSim Process Extraction Skill

Convert unstructured engineering information into the canonical NeqSim JSON format accepted by ProcessSystem.fromJson() and ProcessSystem.fromJsonAndRun().

Core Principle

Extract structured data into a constrained JSON schema. Do NOT write NeqSim Java/Python code.

The JSON schema is finite and well-defined. ProcessSystem.fromJson() handles all NeqSim API calls deterministically. Errors come back as structured, actionable messages.

P&ID operational workflow: When the source is a P&ID and the user asks about a valve action, active train, isolation boundary, bypass, drain, vent, or control-loop behavior, load neqsim-pid-process-operations. Extract both the steady-state topology and the model delta needed to simulate the action.

Exception for route hydraulics: When the source is a STID/E3D/P&ID/stress-isometric line-list table with serial pipe segments, use neqsim.process.equipment.pipeline.routing.PipingRouteBuilder rather than the generic JSON process builder. The route builder preserves line-list segment metadata, K-value minor losses, elevations, and explicit connection topology.

Architecture decision (MANDATORY): Before assembling JSON, classify the process complexity. Small/medium processes (≤ ~15 units, single recycle loop) use a single ProcessSystem. Large processes (multiple plant areas, cross-area recycles, different fluids) must be split into multiple ProcessSystem objects composed inside a ProcessModule, or use pre-built ProcessModuleBaseClass implementations. See Section 16 for the decision guide.


1. Target JSON Schema

Every extraction must produce JSON matching this format:

{
  "fluid": {
    "model": "SRK",
    "temperature": 323.15,
    "pressure": 65.0,
    "mixingRule": "classic",
    "multiPhaseCheck": false,
    "components": {
      "methane": 0.80,
      "ethane": 0.08,
      "propane": 0.05,
      "CO2": 0.03,
      "n-butane": 0.02,
      "nitrogen": 0.01,
      "n-pentane": 0.005,
      "n-hexane": 0.005
    }
  },
  "process": [
    {"type": "Stream", "name": "well stream", "properties": {"flowRate": [75000.0, "kg/hr"]}},
    {"type": "ThreePhaseSeparator", "name": "inlet separator", "inlet": "well stream"},
    {"type": "Compressor", "name": "export compressor", "inlet": "inlet separator.gasOut",
     "properties": {"outletPressure": 120.0, "isentropicEfficiency": 0.78}},
    {"type": "ThrottlingValve", "name": "letdown valve", "inlet": "inlet separator.oilOut",
     "properties": {"outletPressure": 15.0}}
  ],
  "autoRun": true
}

Field Reference

FieldTypeRequiredDescription
fluid.modelstringYesEOS model: SRK, PR, CPA, GERG2008, PCSAFT, UMRPRU
fluid.temperaturenumberYesTemperature in Kelvin
fluid.pressurenumberYesPressure in bara
fluid.mixingRulestringYesUsually "classic" for SRK/PR, "CLASSIC_TX_CPA" for CPA
fluid.multiPhaseCheckbooleanNoSet true for water+HC or 3-phase systems
fluid.componentsobjectYesComponent name → mole fraction (must sum to ~1.0)
process[].typestringYesEquipment type from the Equipment Type Table below
process[].namestringYesUnique equipment tag / display name
process[].inletstringConditionalStream reference (dot-notation). Required for all except 1st Stream
process[].propertiesobjectNoEquipment-specific settings (see Properties Reference)
autoRunbooleanNoSet true to auto-run after building

Multiple Fluids (Named)

For processes with different feed compositions, use the fluids map:

{
  "fluids": {
    "gas_feed": { "model": "SRK", "temperature": 323.15, "pressure": 80.0, "mixingRule": "classic", "components": {"methane": 0.90, "ethane": 0.05, "propane": 0.03, "n-butane": 0.02} },
    "water_feed": { "model": "CPA", "temperature": 293.15, "pressure": 80.0, "mixingRule": "CLASSIC_TX_CPA", "components": {"water": 0.999, "MEG": 0.001} }
  },
  "process": [
    {"type": "Stream", "name": "gas inlet", "fluidRef": "gas_feed", "properties": {"flowRate": [50000.0, "kg/hr"]}},
    {"type": "Stream", "name": "water inlet", "fluidRef": "water_feed", "properties": {"flowRate": [5000.0, "kg/hr"]}}
  ]
}

2. Equipment Type Mapping

Map natural language equipment names to NeqSim JSON type values. Use the longest matching keyword to avoid false matches.

Separation

Natural Language SynonymsNeqSim type
separator, 2-phase separator, two-phase separator, flash drum, flash vessel, KO drum, knock-out drum, knockout drum, scrubber, inlet scrubber, suction scrubber, slug catcher, production scrubber, gas scrubberSeparator
3-phase separator, three-phase separator, production separator, test separator, oil-water-gas separator, 3-phase test separatorThreePhaseSeparator

Compression & Expansion

Natural Language SynonymsNeqSim type
compressor, gas compressor, export compressor, recompressor, booster compressor, LP compressor, HP compressor, 1st stage compressor, 2nd stage compressor, 3rd stage compressor, centrifugal compressor, reciprocating compressorCompressor
expander, turbo-expander, turboexpander, power recovery turbineExpander

Heat Transfer

Natural Language SynonymsNeqSim type
cooler, gas cooler, aftercooler, after-cooler, intercooler, air cooler, fin fan cooler, air-fin cooler, trim cooler, export cooler, overhead condenserCooler
heater, pre-heater, preheater, line heater, electric heater, fired heater, reboiler, trim heaterHeater
heat exchanger, shell and tube, shell-and-tube, plate heat exchanger, plate-fin exchanger, FWHE, gas-gas exchanger, cross-exchanger, economizerHeatExchanger

Valves

Natural Language SynonymsNeqSim type
valve, throttling valve, choke valve, choke, JT valve, Joule-Thomson valve, letdown valve, control valve, pressure control valve, PCV, backpressure valve, production choke, wellhead chokeThrottlingValve

Pumps

Natural Language SynonymsNeqSim type
pump, centrifugal pump, export pump, booster pump, injection pump, feed pump, charge pump, transfer pump, multiphase pumpPump

Piping Routes

Natural Language SynonymsNeqSim target
line list, line-list, route table, STID route, E3D route, stress isometric, pipe run list, serial piping route, compressor suction route, compressor discharge routePipingRouteBuilder

PipingRouteBuilder is not a JSON equipment type. It is a Java/Python-accessible builder for serial route hydraulics. Use it when the input table has from/to nodes, pipe lengths, sizes, elevations, fittings, valves, and K values. Extract the route rows first, then build the route model and export route.toJson() for traceability.

For P&ID valve-action studies, classify each valve before mapping it to NeqSim: control valves become ThrottlingValve equipment, isolation and shutdown valves become scenario switches or boundary states, check valves become directed route constraints, and BDV/PSV/vent valves become relief or blowdown paths.

Mixing & Splitting

Natural Language SynonymsNeqSim type
mixer, mixing tee, junction, merge, combineMixer
splitter, tee, flow divider, bypass teeSplitter
manifold, production manifold, gathering manifold, commingling manifold, subsea manifold, inlet/export headerManifold

Always model a manifold as Manifold, not Mixer/Splitter. Add all inlet streams with addStream(...), then route downstream from a split stream, not getMixedStream(). A single-destination gathering manifold sets one split (setSplitFactors([1.0])) and routes getSplitStream(0); a distributing manifold sets setSplitFactors([...]) (fractions summing to 1) and reads each outlet with getSplitStream(i). getMixedStream() is the internal commingled stream (before the split) — for inspection only. The Manifold also carries header / branch diameters for hydraulics and mechanical design.

Streams

Natural Language SynonymsNeqSim type
stream, feed, inlet, well stream, feed gas, feed stream, input, sourceStream

Other Equipment

Natural Language SynonymsNeqSim type
tank, storage tank, atmospheric tank, settling tank, buffer tankTank
flare, flare stack, flare header, HP flare, LP flareFlare
recycle, recirculationRecycle
ejector, jet pump, steam ejector, gas ejectorEjector
TEG absorber, glycol contactor, TEG contactor, dehydration absorberSimpleTEGAbsorber
reservoir, simple reservoirSimpleReservoir
electrolyzer, water electrolyzer, PEM electrolyzerElectrolyzer
CO2 electrolyzerCO2Electrolyzer
fuel cellFuelCell
wind turbineWindTurbine
solar panel, PV panelSolarPanel
battery storage, battery, BESSBatteryStorage
ammonia reactor, Haber-Bosch reactor, ammonia synthesisAmmoniaSynthesisReactor
distillation column, fractionation column, distillation tower, deethanizer, demethanizer, depropanizer, debutanizer, stripper column, stabilizer columnDistillationColumn
pipe, pipe segment, pipeline, flowline, adiabatic pipeAdiabaticPipe
stream saturator, saturator, water saturatorStreamSaturatorUtil

3. Stream Wiring (Dot-Notation)

Equipment is connected via dot-notation references in the inlet field.

Port Reference Table

Upstream Equipment TypePort SyntaxResolves To
Stream"feed" (name only, no port)The stream directly
Separator"HP Sep.gasOut"Gas outlet stream
Separator"HP Sep.liquidOut"Liquid outlet stream
ThreePhaseSeparator"Inlet Sep.gasOut"Gas outlet
ThreePhaseSeparator"Inlet Sep.oilOut"Oil outlet
ThreePhaseSeparator"Inlet Sep.waterOut"Water outlet
ThreePhaseSeparator"Inlet Sep.liquidOut"Oil outlet (alias)
Compressor"Comp.outlet"Outlet stream
Cooler"Cooler.outlet"Outlet stream
Heater"Heater.outlet"Outlet stream
ThrottlingValve"Valve.outlet"Outlet stream
Pump"Pump.outlet"Outlet stream
Expander"Expander.outlet"Outlet stream
Mixer"Mixer.outlet"Outlet stream
Splitter"Splitter.outlet"Outlet stream (first split)
Splitter"Splitter.split0"Split port 0
Splitter"Splitter.split1"Split port 1
Splitter"Splitter.splitN"Split port N (zero-indexed)
HeatExchanger"HX.outlet"Outlet stream
DistillationColumn"Column.gasOut"Gas (overhead) outlet
DistillationColumn"Column.liquidOut"Liquid (bottoms) outlet
Tank"Tank.outlet"Outlet stream

Wiring Rules

  1. First equipment MUST be a Stream — it gets the fluid from the fluid section
  2. Every subsequent equipment MUST have an inlet reference pointing to a previously defined equipment
  3. For separators, specify the portgasOut, liquidOut, oilOut, waterOut
  4. For single-outlet equipment — use "name.outlet" or just "name" (default resolves to outlet)
  5. Do NOT create circular references — the JSON builder does not support recycle loops directly (add Recycle equipment for convergence)
  6. Branching is supported — multiple equipment can reference different ports of the same separator
  7. Mixer multi-inlet: use "inlets" (plural) — Mixers require "inlets": ["stream1", "stream2"] (array). Do NOT use "inlet" with an array — it will fail with "Array must have size 1"
  8. Separator name without port returns nullresolveStreamReference("HP Sep") returns null. Always use "HP Sep.gasOut" or "HP Sep.liquidOut"

Branching Example

{"type": "ThreePhaseSeparator", "name": "inlet sep", "inlet": "feed"},
{"type": "Compressor", "name": "gas comp", "inlet": "inlet sep.gasOut", ...},
{"type": "ThrottlingValve", "name": "oil valve", "inlet": "inlet sep.oilOut", ...},
{"type": "Pump", "name": "water pump", "inlet": "inlet sep.waterOut", ...}

Mixer Multi-Inlet Example

{"type": "Stream", "name": "gas 1", "properties": {"flowRate": [10000.0, "kg/hr"]}},
{"type": "Stream", "name": "gas 2", "properties": {"flowRate": [5000.0, "kg/hr"]}},
{"type": "Mixer", "name": "gas mixer", "inlets": ["gas 1", "gas 2"]},
{"type": "Cooler", "name": "mixed cooler", "inlet": "gas mixer", "properties": {"outletTemperature": [25.0, "C"]}}

CRITICAL: Use "inlets" (plural key, with array value) for Mixer/multi-inlet equipment. Using "inlet" with an array value will fail.


4. Equipment Properties Reference

Stream

PropertyTypeUnitExample
flowRate[number, "unit"]kg/hr, MSm3/day, Am3/hr[75000.0, "kg/hr"]
temperaturenumberKelvin353.15 (= 80°C)
pressurenumberbara65.0

Compressor

PropertyTypeDefaultDescription
outletPressurenumber (bara)Discharge pressure
isentropicEfficiencynumber (0-1)0.75Isentropic efficiency
polytropicEfficiencynumber (0-1)Polytropic efficiency (alternative)
usePolytropicCalcbooleanfalseUse polytropic head calculation

Cooler / Heater

PropertyTypeDefaultDescription
outTemperaturenumber (K)Outlet temperature in Kelvin
outletTemperature[number, "unit"]Outlet temperature with unit (e.g., [25.0, "C"])

Property Unit Arrays: Equipment properties can be specified with units using the [value, "unit"] array format. This applies to any property that accepts a unit string, such as outletTemperature, flowRate, etc. The JSON builder uses Java reflection to find matching setter methods.

ThrottlingValve

PropertyTypeDefaultDescription
outletPressurenumber (bara)Downstream pressure

Pump

PropertyTypeDefaultDescription
outletPressurenumber (bara)Discharge pressure
isentropicEfficiencynumber (0-1)0.75Isentropic efficiency

Separator / ThreePhaseSeparator

No required properties. Operates at inlet conditions.

Splitter

PropertyTypeDefaultDescription
splitNumberintegerNumber of outlet streams
splitFactors[number, ...]Split factors per outlet (e.g., [0.5, 0.5])

DistillationColumn

PropertyTypeDefaultDescription
numberOfTraysinteger10Number of theoretical trays
hasReboilerbooleantrueWhether column has a reboiler
hasCondenserbooleantrueWhether column has a condenser

HeatExchanger (Multi-Inlet)

HeatExchanger supports two inlets (hot and cold side):

{"type": "HeatExchanger", "name": "gas-gas HX",
 "inlets": ["hot stream", "cold stream"]}

The first inlet becomes the feed stream; the second is set via setFeedStream(1, stream).

AdiabaticPipe

PropertyTypeDefaultDescription
lengthnumber (m)Pipe length in meters
diameternumber (m)Pipe inner diameter in meters

Route-Level Piping Line Lists

When the source has a line-list or stress-isometric table, extract these fields before constructing the route:

Extracted fieldRequiredNotes
segment_idYesLine number, row id, or generated S1, S2
from_node, to_nodeYesEquipment tag, nozzle, tee, manifold, or route node
length, length_unitYesStraight pipe length, not equivalent length
internal_diameter, diameter_unitYesConvert NPS/schedule to internal diameter first
wall_thickness, wall_thickness_unitNoStore if schedule or stress iso gives it
elevation_change, elevation_unitNoPositive uphill, negative downhill
roughness, roughness_unitNoUse default roughness when only piping class is known
minor_lossesNoFittings/valves as {type, k_value} rows
source_refYesDrawing/page/row reference for traceability

Route extraction workflow:

  1. Sort rows in hydraulic flow order from upstream to downstream.
  2. Convert NPS/schedule to internal diameter before calling addSegment(...).
  3. Convert every valve, bend, tee, reducer, strainer, and entry/exit loss to K.
  4. For a route-only study, build the route with PipingRouteBuilder.build(feedStream) and run the returned ProcessSystem.
  5. For a full plant model, call route.addToProcessSystem(process, inletStream) and pass the returned outlet stream to the downstream equipment. Use the overload with source-equipment metadata when the inlet is an upstream equipment outlet stream.
  6. Save route.toJson() and pressure-drop results in the task folder.

Reference guide: docs/process/piping_route_builder.md.


5. Component Name Mapping

Map common aliases to NeqSim database names. The NeqSim name is case-sensitive.

Hydrocarbon Components

Common AliasesNeqSim Name
C1, CH4, methanemethane
C2, C2H6, ethaneethane
C3, C3H8, propanepropane
iC4, i-C4, isobutanei-butane
nC4, n-C4, butane, normal-butanen-butane
iC5, i-C5, isopentanei-pentane
nC5, n-C5, pentane, normal-pentanen-pentane
nC6, n-C6, hexanen-hexane
nC7, n-C7, heptanen-heptane
nC8, n-C8, octanen-octane
nC9, n-C9, nonanen-nonane
nC10, n-C10, decanenC10
nC11 through nC24nC11 through nC24

Non-Hydrocarbon Components

Common AliasesNeqSim Name
CO2, carbon dioxideCO2
H2S, hydrogen sulfide, hydrogen sulphideH2S
N2, nitrogennitrogen
H2, hydrogenhydrogen
O2, oxygenoxygen
Ar, argonargon
He, heliumhelium
H2O, waterwater
Hg, mercurymercury
COS, carbonyl sulfideCOS
SO2, sulfur dioxideSO2

Chemical Additives

Common AliasesNeqSim Name
MEG, monoethylene glycol, ethylene glycolMEG
DEG, diethylene glycolDEG
TEG, triethylene glycolTEG
MeOH, methanolmethanol
EtOH, ethanolethanol
MDEA, methyldiethanolamineMDEA

Aromatics

Common AliasesNeqSim Name
benzene, C6H6benzene
toluene, C7H8, methylbenzenetoluene
cyclohexane, c-C6, cy-C6c-hexane
cyclopentane, c-C5, cy-C5c-C5

6. Unit Conversion Rules

All NeqSim JSON values must be in standard units. Convert before inserting into JSON.

Temperature

Input UnitTo KelvinFormula
°C, degC, CelsiusKT_K = T_C + 273.15
°F, degF, FahrenheitKT_K = (T_F - 32) × 5/9 + 273.15
K, KelvinKIdentity
°R, RankineKT_K = T_R × 5/9

Pressure

Input UnitTo baraFormula
barg, bar gaugebaraP_bara = P_barg + 1.01325
bara, bar absolutebaraIdentity
psia, psi absolutebaraP_bara = P_psia × 0.0689476
psig, psi gaugebaraP_bara = (P_psig + 14.696) × 0.0689476
kPa, kilopascalbaraP_bara = P_kPa / 100.0
MPa, megapascalbaraP_bara = P_MPa × 10.0
atm, atmospherebaraP_bara = P_atm × 1.01325

Flow Rate

Flow rate in JSON uses the [value, "unit"] array format. Supported unit strings:

Unit StringDescription
"kg/hr"Kilograms per hour (mass flow)
"kg/min"Kilograms per minute
"kg/sec"Kilograms per second
"m3/hr"Cubic meters per hour (volume flow)
"Am3/hr"Actual cubic meters per hour
"Sm3/hr"Standard cubic meters per hour
"MSm3/day"Million standard cubic meters per day
"idSm3/day"Ideal standard cubic meters per day
"mole/sec"Moles per second
"mole/hr"Moles per hour

Composition: Weight% to Mole Fraction Conversion

NeqSim uses mole fractions (summing to 1.0) in the JSON components field. If the source provides weight% (wt%), mass fractions, or ppm-by-weight, convert as follows:

Formula:

For each component $i$ with weight fraction $w_i$ and molar mass $M_i$:

$$x_i = \frac{w_i / M_i}{\sum_j (w_j / M_j)}$$

Common Molar Masses (g/mol):

ComponentNeqSim Name$M$ (g/mol)
Methanemethane16.04
Ethaneethane30.07
Propanepropane44.10
n-Butanen-butane58.12
i-Butanei-butane58.12
n-Pentanen-pentane72.15
n-Hexanen-hexane86.18
CO2CO244.01
H2SH2S34.08
Nitrogennitrogen28.01
Waterwater18.02
MEGMEG62.07
TEGTEG150.17
MDEAMDEA119.16

Worked Example:

Input: 70 wt% methane, 20 wt% ethane, 10 wt% propane

Component$w_i$$M_i$$w_i / M_i$$x_i$ (mole frac)
methane0.7016.040.043640.8370
ethane0.2030.070.006650.1275
propane0.1044.100.002270.0355
Total1.000.052161.0000

Result JSON: {"methane": 0.837, "ethane": 0.128, "propane": 0.035}

ppm-by-weight: Convert ppm_w to weight fraction first: $w_i = \text{ppm}_w \times 10^{-6}$

Volume% (gas at standard conditions): Volume% ≈ mole% for ideal gas behavior. Use directly as mole fractions.


7. EOS Model Selection

Choose the thermodynamic model based on the fluid system:

Fluid SystemRecommended ModelMixing Rule
Dry gas, lean gas, simple hydrocarbonsSRK"classic"
Oil systems, general hydrocarbonsPR"classic"
Water + hydrocarbons, MEG/methanol, polarCPA"CLASSIC_TX_CPA"
Fiscal metering, custody transferGERG2008(none needed)
Polymer/associating fluidsPCSAFT"classic"

Decision Rules

  1. If water or glycol is present → use CPA with mixing rule "CLASSIC_TX_CPA" and set multiPhaseCheck: true
  2. If accuracy for gas density/Z-factor is critical → use GERG2008
  3. If heavy oil (C20+) → use PR or SRK with "classic" mixing rule
  4. Default / unknown → use SRK with "classic" mixing rule

8. Extraction Workflow

Follow this step-by-step process for every extraction:

Step 1: Identify the Source Type

  • Text description — paragraph or bullet list describing a process
  • Table / spreadsheet — heat & mass balance, operating data, well test
  • PFD / sketch — process flow diagram (described or as image)
  • Data sheet — equipment data sheet with design conditions
  • Mixed — combination of above

Step 2: Extract Fluid Composition

  1. Look for: mole fractions, mol%, weight%, component tables
  2. Map component names to NeqSim names using the Component Name Mapping table
  3. Normalize to mole fractions summing to 1.0
  4. If weight% given, note it as an assumption (NeqSim uses mole fractions)
  5. If no composition given, flag as missing and use a placeholder

Step 3: Extract Equipment List

  1. Scan for equipment keywords using the Equipment Type Mapping table
  2. Match the longest keyword first (e.g., "three-phase separator" before "separator")
  3. Assign unique names/tags (use P&ID tags if provided, or generate descriptive names)
  4. Record the NeqSim type for each

Step 4: Extract Stream Connectivity

  1. Look for phrases indicating flow direction: "enters", "goes to", "feeds", "is routed to", "flows to", "passes through"
  2. Identify which phase exits which equipment: "gas from the separator", "oil from the 3-phase separator", "compressed gas"
  3. Build dot-notation references: "equipment_name.port"
  4. Verify no orphan streams (every equipment except feed has an inlet)

Step 5: Extract Operating Conditions

  1. Pressures: look for bara, barg, bar, psi, MPa, kPa, atm
  2. Temperatures: look for °C, °F, K, degC, degF
  3. Flow rates: look for kg/hr, t/h, MMSCFD, MSm3/d, Am3/hr
  4. Convert all to NeqSim standard units (K, bara)
  5. Assign to the correct equipment property

Step 6: Assemble JSON

  1. Build the fluid section with model, T, P, mixing rule, and components
  2. Build the process array in topological order (upstream before downstream)
  3. First element MUST be a Stream with the feed fluid
  4. Wire all equipment with inlet references
  5. Set autoRun: true

Step 7: Validate and Report

  1. Check composition sums to ~1.0 (within 0.01)
  2. Check all stream references point to existing equipment
  3. Check no circular references
  4. Compute confidence score (see Confidence Scoring below)
  5. List all assumptions made
  6. List all missing information detected

9. Confidence Scoring

Score the extraction confidence on a 0.0–1.0 scale:

CriterionPoints
Fluid composition explicitly provided+0.25
Feed temperature specified+0.10
Feed pressure specified+0.10
Feed flow rate specified+0.10
All equipment have explicit operating conditions+0.15
Stream topology clearly described+0.15
Equipment tags/names from source (not generated)+0.05
EOS model specified or inferable from context+0.05
No conflicting information in source+0.05

Confidence Bands

ScoreLabelRecommendation
0.80–1.00HighRun directly, review results
0.60–0.79MediumRun but flag assumptions for user review
0.40–0.59LowMore information needed — show what's missing
0.00–0.39Very LowCannot produce reliable simulation — ask user

10. Assumption Defaults

When information is not specified, use these engineering defaults and always track them:

ParameterDefault ValueAssumption Text
EOS modelSRK"Default SRK EOS (not specified in source)"
Mixing ruleclassic"Classic mixing rule assumed"
Feed temperature288.15 K (15°C)"Standard temperature assumed (15°C)"
Feed pressure1.01325 bara"Atmospheric pressure assumed"
Feed flow rate50000 kg/hr"Default flow rate 50000 kg/hr assumed"
Compressor efficiency0.75 (isentropic)"Default isentropic efficiency 0.75 assumed"
Cooler outlet temp308.15 K (35°C)"Default cooler outlet 35°C assumed"
Composition (no data)90% CH4, 5% C2, 3% C3, 2% nC4"Placeholder lean gas composition used"

11. Process Templates

When the extracted topology matches a known pattern, use a template for better reliability.

Template: Gas Dew Point Control

Pattern: cooler → separator → compressor

{
  "fluid": { "model": "SRK", "temperature": "$FEED_T_K$", "pressure": "$FEED_P$", "mixingRule": "classic", "components": "$COMPOSITION$" },
  "process": [
    {"type": "Stream", "name": "feed", "properties": {"flowRate": ["$FLOW$", "kg/hr"]}},
    {"type": "Cooler", "name": "dew point cooler", "inlet": "feed", "properties": {"outTemperature": "$COOLER_T_K$"}},
    {"type": "Separator", "name": "cold separator", "inlet": "dew point cooler.outlet"},
    {"type": "Compressor", "name": "export compressor", "inlet": "cold separator.gasOut", "properties": {"outletPressure": "$EXPORT_P$", "isentropicEfficiency": 0.78}}
  ],
  "autoRun": true
}

Template: Two-Stage HP/LP Separation

Pattern: 3-phase sep → gas compression + oil letdown → LP sep → recompression

{
  "fluid": { "model": "SRK", "temperature": "$FEED_T_K$", "pressure": "$HP_P$", "mixingRule": "classic", "components": "$COMPOSITION$" },
  "process": [
    {"type": "Stream", "name": "well stream", "properties": {"flowRate": ["$FLOW$", "kg/hr"]}},
    {"type": "ThreePhaseSeparator", "name": "HP separator", "inlet": "well stream"},
    {"type": "Cooler", "name": "gas cooler", "inlet": "HP separator.gasOut", "properties": {"outTemperature": 308.15}},
    {"type": "Compressor", "name": "export compressor", "inlet": "gas cooler.outlet", "properties": {"outletPressure": "$EXPORT_P$", "isentropicEfficiency": 0.78}},
    {"type": "ThrottlingValve", "name": "HP-LP valve", "inlet": "HP separator.oilOut", "properties": {"outletPressure": "$LP_P$"}},
    {"type": "Separator", "name": "LP separator", "inlet": "HP-LP valve.outlet"},
    {"type": "Compressor", "name": "LP recompressor", "inlet": "LP separator.gasOut", "properties": {"outletPressure": "$HP_P$", "isentropicEfficiency": 0.75}}
  ],
  "autoRun": true
}

Template: Multi-Stage Compression with Intercooling

Pattern: compressor → cooler → scrubber → compressor → cooler → scrubber → ... (N stages)

Build dynamically with equal pressure ratio per stage:

  • ratio_per_stage = (P_out / P_in) ^ (1/N)
  • stage_P[i] = P_in × ratio_per_stage^i
  • Each stage: CompressorCooler (to intercooler temp) → Separator (scrub condensate)
  • Last stage: Compressor only (no aftercooler/scrubber, unless specified)

Template: Gas Cooling and JT Expansion

Pattern: cooler → separator → JT valve → cold separator

{
  "fluid": { "model": "SRK", "temperature": "$FEED_T_K$", "pressure": "$FEED_P$", "mixingRule": "classic", "components": "$COMPOSITION$" },
  "process": [
    {"type": "Stream", "name": "feed", "properties": {"flowRate": ["$FLOW$", "kg/hr"]}},
    {"type": "Cooler", "name": "pre-cooler", "inlet": "feed", "properties": {"outTemperature": "$PRECOOL_T_K$"}},
    {"type": "Separator", "name": "inlet scrubber", "inlet": "pre-cooler.outlet"},
    {"type": "ThrottlingValve", "name": "JT valve", "inlet": "inlet scrubber.gasOut", "properties": {"outletPressure": "$JT_P$"}},
    {"type": "Separator", "name": "cold separator", "inlet": "JT valve.outlet"}
  ],
  "autoRun": true
}

Template: Simple Oil Stabilization

Pattern: 3-phase sep → valve → flash drum → valve → atmospheric flash

{
  "fluid": { "model": "SRK", "temperature": "$FEED_T_K$", "pressure": "$HP_P$", "mixingRule": "classic", "multiPhaseCheck": true, "components": "$COMPOSITION$" },
  "process": [
    {"type": "Stream", "name": "well fluid", "properties": {"flowRate": ["$FLOW$", "kg/hr"]}},
    {"type": "ThreePhaseSeparator", "name": "production separator", "inlet": "well fluid"},
    {"type": "ThrottlingValve", "name": "1st stage valve", "inlet": "production separator.oilOut", "properties": {"outletPressure": "$STAGE2_P$"}},
    {"type": "Separator", "name": "2nd stage separator", "inlet": "1st stage valve.outlet"},
    {"type": "ThrottlingValve", "name": "2nd stage valve", "inlet": "2nd stage separator.liquidOut", "properties": {"outletPressure": "$STAGE3_P$"}},
    {"type": "Separator", "name": "stabilizer", "inlet": "2nd stage valve.outlet"}
  ],
  "autoRun": true
}

Template: Subsea Tieback (Well → Pipeline → Platform)

Pattern: well stream → choke → pipeline → separator

{
  "fluid": { "model": "SRK", "temperature": "$WELLHEAD_T_K$", "pressure": "$WELLHEAD_P$", "mixingRule": "classic", "multiPhaseCheck": true, "components": "$COMPOSITION$" },
  "process": [
    {"type": "Stream", "name": "well stream", "properties": {"flowRate": ["$FLOW$", "kg/hr"]}},
    {"type": "ThrottlingValve", "name": "production choke", "inlet": "well stream", "properties": {"outletPressure": "$CHOKE_P$"}},
    {"type": "Heater", "name": "pipeline heat loss", "inlet": "production choke.outlet", "properties": {"outTemperature": "$ARRIVAL_T_K$"}},
    {"type": "ThreePhaseSeparator", "name": "inlet separator", "inlet": "pipeline heat loss.outlet"}
  ],
  "autoRun": true
}

Template: TEG Dehydration

Pattern: wet gas → TEG absorber ← lean TEG; dry gas out, rich TEG out

Note: SimpleTEGAbsorber requires two input streams added via addGasInStream() and addSolventInStream(). The JSON builder currently wires via the inlet field, so for TEG dehydration, build the lean TEG stream as a separate feed with a TEG+water fluid.

{
  "fluids": {
    "wet_gas": {
      "model": "SRK", "temperature": "$FEED_T_K$", "pressure": "$FEED_P$",
      "mixingRule": "classic",
      "components": "$GAS_COMPOSITION_WITH_WATER$"
    },
    "lean_teg": {
      "model": "CPA", "temperature": "$TEG_T_K$", "pressure": "$FEED_P$",
      "mixingRule": "CLASSIC_TX_CPA",
      "components": {"TEG": 0.99, "water": 0.01}
    }
  },
  "process": [
    {"type": "Stream", "name": "wet gas feed", "fluidRef": "wet_gas", "properties": {"flowRate": ["$GAS_FLOW$", "kg/hr"]}},
    {"type": "Stream", "name": "lean TEG", "fluidRef": "lean_teg", "properties": {"flowRate": ["$TEG_FLOW$", "kg/hr"]}},
    {"type": "SimpleTEGAbsorber", "name": "TEG absorber", "inlet": "wet gas feed",
      "properties": {"numberOfStages": "$STAGES$", "stageEfficiency": 0.5}},
    {"type": "Heater", "name": "TEG reboiler sim", "inlet": "TEG absorber.liquidOut",
      "properties": {"outTemperature": "$REBOILER_T_K$"}}
  ],
  "autoRun": true
}

Typical defaults: 3–5 stages, stage efficiency 0.5, lean TEG flow 5–10× water to remove, reboiler at ~200°C (473 K). TEG purity: 99–99.5 wt%.

Template: NGL Recovery (Turbo-Expander + Demethanizer)

Pattern: gas inlet → cooler → expander → demethanizer column; overhead = sales gas, bottoms = NGL

{
  "fluid": { "model": "SRK", "temperature": "$FEED_T_K$", "pressure": "$FEED_P$", "mixingRule": "classic", "components": "$COMPOSITION$" },
  "process": [
    {"type": "Stream", "name": "inlet gas", "properties": {"flowRate": ["$FLOW$", "kg/hr"]}},
    {"type": "Cooler", "name": "gas chiller", "inlet": "inlet gas", "properties": {"outTemperature": "$CHILLER_T_K$"}},
    {"type": "Separator", "name": "cold separator", "inlet": "gas chiller.outlet"},
    {"type": "Expander", "name": "turbo-expander", "inlet": "cold separator.gasOut", "properties": {"outletPressure": "$EXPANDER_P$", "isentropicEfficiency": 0.85}},
    {"type": "ThrottlingValve", "name": "liquid JT valve", "inlet": "cold separator.liquidOut", "properties": {"outletPressure": "$EXPANDER_P$"}},
    {"type": "Mixer", "name": "column feed mixer", "inlets": ["turbo-expander.outlet", "liquid JT valve.outlet"]},
    {"type": "Separator", "name": "demethanizer sim", "inlet": "column feed mixer.outlet"},
    {"type": "Compressor", "name": "residue compressor", "inlet": "demethanizer sim.gasOut", "properties": {"outletPressure": "$SALES_P$", "isentropicEfficiency": 0.78}}
  ],
  "autoRun": true
}

Note: For a rigorous demethanizer, replace the Separator with a Column (DistillationColumn). The simplified version uses a cold separator as a proxy. Typical expander outlet: 15–25 bara, efficiency 0.82–0.88, chiller to –30°C to –40°C.

Template: Acid Gas Removal (Amine Sweetening)

Pattern: sour gas → amine absorber ← lean amine; sweet gas out, rich amine to regenerator

Note: Uses SimpleTEGAbsorber which works for generic absorption. For amine-specific thermodynamics, the CPA EOS with MDEA is recommended.

{
  "fluids": {
    "sour_gas": {
      "model": "CPA", "temperature": "$FEED_T_K$", "pressure": "$FEED_P$",
      "mixingRule": "CLASSIC_TX_CPA",
      "components": "$SOUR_GAS_COMPOSITION$"
    },
    "lean_amine": {
      "model": "CPA", "temperature": "$AMINE_T_K$", "pressure": "$FEED_P$",
      "mixingRule": "CLASSIC_TX_CPA",
      "components": {"MDEA": 0.40, "water": 0.60}
    }
  },
  "process": [
    {"type": "Stream", "name": "sour gas feed", "fluidRef": "sour_gas", "properties": {"flowRate": ["$GAS_FLOW$", "kg/hr"]}},
    {"type": "Stream", "name": "lean amine", "fluidRef": "lean_amine", "properties": {"flowRate": ["$AMINE_FLOW$", "kg/hr"]}},
    {"type": "SimpleTEGAbsorber", "name": "amine absorber", "inlet": "sour gas feed",
      "properties": {"numberOfStages": "$STAGES$", "stageEfficiency": 0.5}},
    {"type": "Heater", "name": "amine regenerator sim", "inlet": "amine absorber.liquidOut",
      "properties": {"outTemperature": "$REGEN_T_K$"}}
  ],
  "autoRun": true
}

Typical defaults: 10–20 stages, MDEA 40–50 wt%, amine circulation rate 50–100 L/kg acid gas, regenerator at 120–130°C. Use CPA EOS with mixing rule "CLASSIC_TX_CPA" for polar systems.

Template: Produced Water Treatment (Degassing)

Pattern: produced water → heater → 3-phase separator → water stripper or flash drum

{
  "fluid": { "model": "CPA", "temperature": "$FEED_T_K$", "pressure": "$FEED_P$", "mixingRule": "CLASSIC_TX_CPA", "multiPhaseCheck": true,
    "components": "$WATER_OIL_GAS_COMPOSITION$" },
  "process": [
    {"type": "Stream", "name": "produced water", "properties": {"flowRate": ["$FLOW$", "kg/hr"]}},
    {"type": "Heater", "name": "water heater", "inlet": "produced water", "properties": {"outTemperature": "$HEATER_T_K$"}},
    {"type": "ThreePhaseSeparator", "name": "water degasser", "inlet": "water heater.outlet"},
    {"type": "ThrottlingValve", "name": "flash valve", "inlet": "water degasser.waterOut", "properties": {"outletPressure": "$FLASH_P$"}},
    {"type": "Separator", "name": "atmospheric flash", "inlet": "flash valve.outlet"}
  ],
  "autoRun": true
}

Typical defaults: Produced water at 60–80°C, degassing at 1–3 bara. Use CPA EOS with mixing rule "CLASSIC_TX_CPA" when water is a major component. Composition: primarily water (>95 mol%) with dissolved methane, CO2, and trace hydrocarbons.


12. Worked Examples

Example 1: Simple Text Description

Input:

"Feed gas at 80 bara and 40°C enters a cooler to 15°C. The cooled stream goes to a separator. Gas from the separator is compressed to 120 bara."

Extraction:

StepExtracted
CompositionNOT PROVIDED → flag as missing, use placeholder
Feed T40°C → 313.15 K
Feed P80 bara → 80.0
CoolerTarget 15°C → outTemperature: 288.15
SeparatorAfter cooler, takes gas port
Compressor120 bara → outletPressure: 120.0

Output JSON:

{
  "fluid": {
    "model": "SRK", "temperature": 313.15, "pressure": 80.0,
    "mixingRule": "classic",
    "components": {"methane": 0.90, "ethane": 0.05, "propane": 0.03, "n-butane": 0.02}
  },
  "process": [
    {"type": "Stream", "name": "feed gas", "properties": {"flowRate": [50000.0, "kg/hr"]}},
    {"type": "Cooler", "name": "gas cooler", "inlet": "feed gas", "properties": {"outTemperature": 288.15}},
    {"type": "Separator", "name": "scrubber", "inlet": "gas cooler.outlet"},
    {"type": "Compressor", "name": "export compressor", "inlet": "scrubber.gasOut", "properties": {"outletPressure": 120.0, "isentropicEfficiency": 0.75}}
  ],
  "autoRun": true
}

Report:

  • Confidence: 0.50 (temperature ✓, pressure ✓, topology ✓, but no composition, no flow rate)
  • Assumptions: Placeholder composition (90% CH4), default flow rate 50000 kg/hr, default compressor efficiency 0.75, SRK EOS
  • Missing: Feed composition, feed flow rate

Example 2: Process with Composition

Input:

"The well stream arrives at 65 bara and 80°C with 75000 kg/hr. Composition: 80% methane, 8% ethane, 5% propane, 3% CO2, 2% n-butane, 1% N2, 0.5% n-pentane, 0.5% n-hexane. It enters a 3-phase separator. Gas goes to a compressor at 120 bara. Oil goes through a letdown valve to 15 bara."

Output JSON:

{
  "fluid": {
    "model": "SRK", "temperature": 353.15, "pressure": 65.0,
    "mixingRule": "classic",
    "components": {
      "methane": 0.80, "ethane": 0.08, "propane": 0.05,
      "CO2": 0.03, "n-butane": 0.02, "nitrogen": 0.01,
      "n-pentane": 0.005, "n-hexane": 0.005
    }
  },
  "process": [
    {"type": "Stream", "name": "well stream", "properties": {"flowRate": [75000.0, "kg/hr"]}},
    {"type": "ThreePhaseSeparator", "name": "inlet separator", "inlet": "well stream"},
    {"type": "Compressor", "name": "gas compressor", "inlet": "inlet separator.gasOut", "properties": {"outletPressure": 120.0, "isentropicEfficiency": 0.75}},
    {"type": "ThrottlingValve", "name": "letdown valve", "inlet": "inlet separator.oilOut", "properties": {"outletPressure": 15.0}}
  ],
  "autoRun": true
}

Report:

  • Confidence: 0.85
  • Assumptions: SRK EOS (not specified), default compressor efficiency 0.75
  • Missing: Compressor efficiency, separator operating temperature

Example 3: From a Heat & Mass Balance Table

Input Table:

StreamPhaseT (°C)P (barg)Flow (t/h)CH4 mol%C2H6 mol%C3H8 mol%CO2 mol%
FeedV+L604812082.57.24.12.3

Equipment List: HP separator → export compressor (95 barg) → aftercooler (35°C) → scrubber

Extraction:

  • T = 60°C → 333.15 K; P = 48 barg → 49.01325 bara
  • Flow = 120 t/h → 120000 kg/hr
  • Composition: map aliases, normalize remainder to nC4
  • Export P = 95 barg → 96.01325 bara

Output JSON:

{
  "fluid": {
    "model": "SRK", "temperature": 333.15, "pressure": 49.01325,
    "mixingRule": "classic",
    "components": {
      "methane": 0.825, "ethane": 0.072, "propane": 0.041,
      "CO2": 0.023, "n-butane": 0.039
    }
  },
  "process": [
    {"type": "Stream", "name": "feed", "properties": {"flowRate": [120000.0, "kg/hr"]}},
    {"type": "ThreePhaseSeparator", "name": "HP separator", "inlet": "feed"},
    {"type": "Compressor", "name": "export compressor", "inlet": "HP separator.gasOut", "properties": {"outletPressure": 96.01325, "isentropicEfficiency": 0.75}},
    {"type": "Cooler", "name": "aftercooler", "inlet": "export compressor.outlet", "properties": {"outTemperature": 308.15}},
    {"type": "Separator", "name": "export scrubber", "inlet": "aftercooler.outlet"}
  ],
  "autoRun": true
}

Report:

  • Confidence: 0.90 (composition ✓, conditions ✓, flow ✓, equipment clear)
  • Assumptions: SRK EOS, default compressor efficiency, remaining 3.9 mol% assigned to n-butane
  • Missing: Compressor efficiency, individual heavy component split

Example 4: From Equipment Tag List (ProcessPilot style)

Input:

Separators: 20VA001 (HP, 50 barg, 60°C), 20VB001 (LP, 14 barg)
Compressors: 23KA600 (discharge 60 barg, eff 0.78)
Valves: 20XV001 (outlet 15 barg)
Topology: feed→20VA001, 20VA001.gasOut→23KA600, 20VA001.oilOut→20XV001, 20XV001.outlet→20VB001
Feed: 100000 kg/hr, 60°C, 50 barg

Output JSON:

{
  "fluid": {
    "model": "SRK", "temperature": 333.15, "pressure": 51.01325,
    "mixingRule": "classic",
    "components": {"methane": 0.90, "ethane": 0.05, "propane": 0.03, "n-butane": 0.02}
  },
  "process": [
    {"type": "Stream", "name": "feed", "properties": {"flowRate": [100000.0, "kg/hr"]}},
    {"type": "ThreePhaseSeparator", "name": "20VA001", "inlet": "feed"},
    {"type": "Compressor", "name": "23KA600", "inlet": "20VA001.gasOut", "properties": {"outletPressure": 61.01325, "isentropicEfficiency": 0.78}},
    {"type": "ThrottlingValve", "name": "20XV001", "inlet": "20VA001.oilOut", "properties": {"outletPressure": 16.01325}},
    {"type": "Separator", "name": "20VB001", "inlet": "20XV001.outlet"}
  ],
  "autoRun": true
}

Example 5: Multi-Stage Compression (from a design report)

Input:

"Compress LP gas from 5 bara to 150 bara in 3 stages with intercooling to 30°C. Gas composition: 88% methane, 6% ethane, 3% propane, 2% CO2, 1% nitrogen. Feed is 25000 kg/hr at 25°C."

Extraction:

  • 3 stages, ratio = (150/5)^(1/3) = 3.107
  • Stage 1: 5 → 15.5 bara
  • Stage 2: 15.5 → 48.2 bara
  • Stage 3: 48.2 → 150 bara

Output JSON:

{
  "fluid": {
    "model": "SRK", "temperature": 298.15, "pressure": 5.0,
    "mixingRule": "classic",
    "components": {"methane": 0.88, "ethane": 0.06, "propane": 0.03, "CO2": 0.02, "nitrogen": 0.01}
  },
  "process": [
    {"type": "Stream", "name": "feed gas", "properties": {"flowRate": [25000.0, "kg/hr"]}},
    {"type": "Compressor", "name": "compressor stage 1", "inlet": "feed gas", "properties": {"outletPressure": 15.5, "isentropicEfficiency": 0.78}},
    {"type": "Cooler", "name": "aftercooler 1", "inlet": "compressor stage 1.outlet", "properties": {"outTemperature": 303.15}},
    {"type": "Separator", "name": "scrubber 1", "inlet": "aftercooler 1.outlet"},
    {"type": "Compressor", "name": "compressor stage 2", "inlet": "scrubber 1.gasOut", "properties": {"outletPressure": 48.2, "isentropicEfficiency": 0.78}},
    {"type": "Cooler", "name": "aftercooler 2", "inlet": "compressor stage 2.outlet", "properties": {"outTemperature": 303.15}},
    {"type": "Separator", "name": "scrubber 2", "inlet": "aftercooler 2.outlet"},
    {"type": "Compressor", "name": "compressor stage 3", "inlet": "scrubber 2.gasOut", "properties": {"outletPressure": 150.0, "isentropicEfficiency": 0.78}}
  ],
  "autoRun": true
}

13. Output Format

Every extraction MUST produce three things:

1. The NeqSim JSON

The complete JSON object ready for ProcessSystem.fromJsonAndRun().

2. Extraction Report

Present as a structured summary:

EXTRACTION REPORT
─────────────────
Source type:  [text / table / PFD / data sheet / mixed]
Confidence:  [0.XX] — [High / Medium / Low / Very Low]
Equipment:   [N] units extracted
Streams:     [N] connections wired

Assumptions Used:
  - [assumption 1]
  - [assumption 2]
  ...

Missing Information:
  - [missing item 1]
  - [missing item 2]
  ...

Warnings:
  - [warning 1]
  ...

3. Simulation Results (after running)

SIMULATION RESULTS
──────────────────
Status: [SUCCESS / ERROR]

Equipment        T (°C)    P (bara)   Flow (kg/hr)   Notes
─────────       ──────    ────────   ────────────   ─────
Feed              80.0      65.0      75000          —
HP Separator      80.0      65.0      —              Gas + Oil split
Gas Compressor   145.2     120.0      62000          Power: 2450 kW
Oil Valve         55.3      15.0      13000          —

14. Error Handling

If ProcessSystem.fromJsonAndRun() returns errors, interpret them:

Error CodeMeaningLikely Fix
JSON_PARSE_ERRORMalformed JSONCheck JSON syntax (missing comma, unmatched brace)
MISSING_PROCESSNo process arrayAdd the process equipment array
MISSING_TYPEEquipment has no typeAdd "type": "..." to the unit definition
STREAM_NOT_FOUNDInlet reference points to nonexistent equipmentCheck equipment order and name spelling
FLUID_NOT_FOUNDfluidRef points to undefined fluidDefine the fluid in the fluids section
NO_FLUIDStream has no fluidDefine a fluid section or add fluidRef
UNKNOWN_MODELUnrecognized EOS modelUse: SRK, PR, CPA, GERG2008, PCSAFT, UMRPRU
UNIT_ERROREquipment creation failedCheck property values and types
SIMULATION_ERRORRuntime failure during process.run()Check pressure/temperature ranges, compositions

Tolerant Error Handling

The JSON builder uses tolerant error handling for stream wiring: when a stream reference cannot be resolved (e.g., the upstream unit was skipped), the equipment is removed from the process rather than failing the entire build. These show up as warnings (not errors) in SimulationResult. Similarly, if process.run() throws an exception, it is caught and returned as a warning — the process is still returned in its partially-run state.

This means result.isSuccess() can be true even when result.hasWarnings() is also true. Always check warnings to identify any equipment that was skipped:

result = ProcessSystem.fromJson(json_str)
if result.hasWarnings():
    for w in result.getWarnings():
        print(f"WARNING [{w.getCode()}]: {w.getMessage()}")

15. Operating Data Bridge Patterns

Patterns for converting operating data from external sources (Excel, CSV, historians) into NeqSim JSON.

From Excel / CSV Tabular Data

When operating data arrives as a table (common in FEED reports, well tests, plant data):

Step 1 — Identify columns:

Column TypeMaps To
Stream name / tag"name" in process array
Temperature (with unit)"outTemperature" (convert to K)
Pressure (with unit)"outletPressure" / fluid "pressure"
Flow rate (with unit)"flowRate" property
Composition columns (CH4 mol%, C2H6 mol%, ...)"components" in fluid
Equipment tag (e.g., 20VA001)"name"
Equipment type (separator, compressor, ...)"type" (via equipment mapping)

Step 2 — Build JSON from rows:

Excel Row:                          NeqSim JSON Unit:
┌─────────────────────────────┐    ┌──────────────────────────────────┐
│ Tag: 23KA601                │ →  │ "name": "23KA601"                │
│ Type: Centrifugal compressor│ →  │ "type": "Compressor"             │
│ Suction P: 48 barg          │ →  │ (inlet stream pressure)          │
│ Discharge P: 95 barg        │ →  │ "outletPressure": 96.01325       │
│ Efficiency: 78%             │ →  │ "isentropicEfficiency": 0.78     │
│ Inlet: from 20VA001 gas     │ →  │ "inlet": "20VA001.gasOut"        │
└─────────────────────────────┘    └──────────────────────────────────┘

Step 3 — Handle multi-row compositions:

If composition is spread across rows (one row per component):

Component    mol%
methane      82.5
ethane        7.2
propane       4.1
CO2           2.3
remainder     3.9  ← assign to n-butane or split to nC4/nC5

Convert to: {"methane": 0.825, "ethane": 0.072, "propane": 0.041, "CO2": 0.023, "n-butane": 0.039}

From Plant Historian Data (PI / IP.21)

When operating data comes from realtime historians:

Tag Mapping Pattern:

Historian Tag            → NeqSim Parameter
─────────────           ─────────────────
PT-20001.PV (bara)      → feed stream pressure
TT-20001.PV (°C)        → feed stream temperature (+273.15)
FT-20001.PV (kg/hr)     → feed stream flowRate
AT-20001-CH4.PV (mol%)  → fluid component "methane" (/100)
AT-20001-C2H6.PV (mol%) → fluid component "ethane" (/100)
PT-23001.PV (bara)      → compressor outletPressure

JSON with Historian Placeholder Tags:

Use "$TAG:tagname$" placeholders that a data bridge fills at runtime:

{
  "fluid": {
    "model": "SRK",
    "temperature": "$TAG:TT-20001.PV+273.15$",
    "pressure": "$TAG:PT-20001.PV$",
    "mixingRule": "classic",
    "components": {
      "methane": "$TAG:AT-20001-CH4.PV/100$",
      "ethane": "$TAG:AT-20001-C2H6.PV/100$"
    }
  },
  "process": [
    {"type": "Stream", "name": "feed", "properties": {"flowRate": ["$TAG:FT-20001.PV$", "kg/hr"]}},
    {"type": "Compressor", "name": "23KA001", "inlet": "feed",
      "properties": {"outletPressure": "$TAG:PT-23001.PV$"}}
  ]
}

Python Data Bridge Example (pandas):

import pandas as pd
import json

# Load operating data from Excel
df = pd.read_excel("operating_data.xlsx", sheet_name="Well Test")

# Build fluid composition from columns
composition = {}
component_map = {"CH4": "methane", "C2H6": "ethane", "C3H8": "propane",
                 "CO2": "CO2", "N2": "nitrogen", "H2S": "H2S"}
for col, neqsim_name in component_map.items():
    if col in df.columns:
        val = float(df[col].iloc[0])
        if val > 1.0:  # Likely mol% not fraction
            val /= 100.0
        composition[neqsim_name] = round(val, 6)

# Normalize
total = sum(composition.values())
composition = {k: round(v / total, 6) for k, v in composition.items()}

# Build JSON
neqsim_json = {
    "fluid": {
        "model": "SRK",
        "temperature": float(df["Temperature_C"].iloc[0]) + 273.15,
        "pressure": float(df["Pressure_bara"].iloc[0]),
        "mixingRule": "classic",
        "components": composition
    },
    "process": [
        {"type": "Stream", "name": "feed",
         "properties": {"flowRate": [float(df["FlowRate_kghr"].iloc[0]), "kg/hr"]}}
        # ... add equipment from equipment sheet
    ],
    "autoRun": True
}

Handling Common Data Issues

IssueDetectionFix
Composition sums to ~100 not ~1Sum > 1.5Divide all values by 100
Composition sums to < 0.95Sum < 0.95Assign remainder to heaviest component, flag assumption
Pressure in barg not baraValues look low (< 1 for HP systems)Add 1.01325 to convert
Temperature in °C not KValues < 200 for processAdd 273.15
Missing flow unitsNo unit columnDefault to kg/hr, flag assumption
ppm trace componentsValues > 100 in composition columnConvert: mol_frac = ppm × 1e-6

16. ProcessSystem vs ProcessModule Architecture

NeqSim has three levels of process model organization. The extraction agent MUST choose the right level before assembling JSON or Python code.

Decision Guide

Process ComplexityUnitsRecyclesArchitectureJSON Support
Simple / linear1–80–1Single ProcessSystemYES — use fromJsonAndRun()
Medium5–150–1Single ProcessSystemYES — use fromJsonAndRun()
Large / multi-area10–50+0–3+ProcessModule composing multiple ProcessSystemsNO — must use Python/Java code
Reusable subsystemanyinternalProcessModuleBaseClass subclassNO — must use Python/Java code

Decision rules (apply in order):

  1. Can the entire process be expressed as a single linear/branching chain? → Single ProcessSystem via JSON.
  2. Does the process have ≤ 1 recycle loop with ≤ 15 total units? → Single ProcessSystem via JSON (recycles supported via Recycle equipment type).
  3. Does the process have distinct plant areas (e.g., separation train + compression + dehydration + export) with different fluids or cross-area recycles? → Split into multiple ProcessSystem objects inside a ProcessModule.
  4. Does a pre-built module exist (separation, TEG dehydration, CO2 removal, adsorption)? → Use the corresponding ProcessModuleBaseClass subclass.
  5. Does the process require > 50 units or nested recycles across areas? → Use nested ProcessModule containing other ProcessModules.

Architecture A: Single ProcessSystem (JSON-compatible)

When to use: Most extraction scenarios. The JSON builder creates a single ProcessSystem with equipment wired via stream references.

┌──────────────────────────────────────────┐
│           ProcessSystem                  │
│                                          │
│  Stream → Cooler → Separator → Compressor│
│                      │                   │
│                      └→ Valve → LP Sep   │
└──────────────────────────────────────────┘

Limitations of single ProcessSystem:

  • All equipment shares one feed fluid definition
  • One convergence loop (adjusters + recycles solved together)
  • No sub-module encapsulation
  • Gets unwieldy above ~15-20 units

Architecture B: ProcessModuleBaseClass (Reusable Black-Box Modules)

When to use: The source describes a standard process subsystem that matches one of NeqSim's pre-built modules. Use the module directly rather than rebuilding from individual equipment.

Available pre-built modules:

Module ClassPurposeInput StreamsOutput Streams
SeparationTrainModuleMulti-stage HP/MP/LP separation"feed stream""gas exit stream", "oil exit stream"
SeparationTrainModuleSimpleSimplified 2-stage separation"feed stream""gas exit stream", "oil exit stream"
GlycolDehydrationlModuleTEG dehydration (absorber + stripper + regen)"gasStreamToAbsorber", "strippingGas""gasStreamFromAbsorber", "liquidFromStripper"
CO2RemovalModuleCO2 absorption/stripping"streamToAbsorber""streamFromAbsorber"
AdsorptionDehydrationlModuleAdsorption dehydration with multiple beds"gasStreamToAdsorber""gasStreamFromAdsorber"
DPCUModuleDew Point Control Unit (expander + column)"feed stream""gas exit stream", "oil exit stream"
PropaneCoolingModulePropane refrigeration cycle"refrigerant""refrigerant"
MEGReclaimerModuleMEG reclamation"streamToReclaimer""streamToWaterRemoval"
MixerGasProcessingModuleGas processing with glycol injection"feed stream", "glycol feed stream""gas exit stream", "oil exit stream"
WellFluidModuleWell fluid characterization & separation"feed stream"single outlet

Pattern — module as equipment in ProcessSystem:

# A module IS a ProcessEquipmentInterface — add it to a ProcessSystem
from neqsim import jneqsim

SepModule = jneqsim.process.processmodel.processmodules.SeparationTrainModule

sep_module = SepModule("separation train")
sep_module.addInputStream("feed stream", feed_stream)
sep_module.setSpecification("pressure1", 65.0)  # HP sep pressure
sep_module.setSpecification("pressure2", 25.0)  # MP sep pressure
sep_module.setSpecification("pressure3", 5.0)   # LP sep pressure

process = jneqsim.process.processmodel.ProcessSystem()
process.add(feed_stream)
process.add(sep_module)  # Module treated as 1 equipment
process.run()

# Get output streams from module
gas_out = sep_module.getOutputStream("gas exit stream")
oil_out = sep_module.getOutputStream("oil exit stream")

Key API:

  • addInputStream(String name, StreamInterface stream) — wire input by port name
  • getOutputStream(String name) — get output by port name
  • getOperations()ProcessSystem — access the internal process
  • getUnit(String name) — access individual equipment inside the module
  • initializeModule() — called automatically when added to a ProcessSystem

Architecture C: ProcessModule (Multi-System Composition)

When to use: The source describes a large facility with multiple distinct process areas that need separate convergence or different thermodynamic models.

┌─────────────────────────────────────────────────────────┐
│                    ProcessModule                        │
│                                                         │
│  ┌──────────────┐   ┌──────────────┐   ┌─────────────┐ │
│  │ ProcessSystem │──►│ ProcessSystem │──►│ProcessSystem│ │
│  │  Separation   │   │  Compression │   │ Dehydration │ │
│  └──────────────┘   └──────────────┘   └─────────────┘ │
│         │                                    │          │
│         └──────── recycle stream ─────────────┘          │
└─────────────────────────────────────────────────────────┘

Pattern — composing multiple ProcessSystems:

from neqsim import jneqsim

ProcessSystem = jneqsim.process.processmodel.ProcessSystem
ProcessModule = jneqsim.process.processmodel.ProcessModule

# Build each plant area as a separate ProcessSystem
sep_system = ProcessSystem()
sep_system.add(feed_stream)
sep_system.add(hp_separator)
sep_system.add(lp_separator)

comp_system = ProcessSystem()
comp_system.add(gas_from_sep)      # shared stream object links the systems
comp_system.add(compressor_1)
comp_system.add(aftercooler_1)
comp_system.add(compressor_2)

dehyd_system = ProcessSystem()
dehyd_system.add(compressed_gas)   # shared stream object
dehyd_system.add(teg_absorber)
dehyd_system.add(dry_gas_stream)

# Compose into a ProcessModule
plant = ProcessModule("Gas Processing Plant")
plant.add(sep_system)
plant.add(comp_system)
plant.add(dehyd_system)
plant.run()  # Runs sub-systems in order; handles cross-system recycles

# Access any equipment across all sub-systems
comp = plant.getUnit("compressor 1")
print(f"Power: {comp.getPower('kW'):.0f} kW")

Key API for ProcessModule:

MethodDescription
add(ProcessSystem)Add a sub-system
add(ProcessModule)Nest another module
run()Run all sub-systems (handles cross-system recycles)
getUnit(String name)Find equipment across all sub-systems
getAllProcessSystems()Recursively list all ProcessSystems
getSubSystemCount()Number of sub-systems + nested modules
hasRecycleLoops()Detect cross-system recycles
validateStructure()Check for errors (empty modules, disconnected systems)
buildModelGraph()Build directed graph for topology analysis
getCalculationOrder()Topological sort across all sub-systems
checkMassBalance(String unit)Mass balance verification
copy()Deep serialization copy

Cross-system wiring mechanism: Systems connect through shared stream objects. When equipment in system A produces an outlet stream, that same Java object is used as the inlet to equipment in system B. The ProcessModelGraphBuilder automatically detects these cross-system links.

Recycle convergence: If recycles span multiple sub-systems, ProcessModule.run() flattens all units and iterates until all recycles converge (max 100 iterations).

Extraction Workflow Integration

When extracting from a source document, add a Step 0 before the normal extraction:

Step 0: Classify Process Complexity

  1. Count the total number of equipment items mentioned
  2. Identify distinct plant areas (separation, compression, dehydration, etc.)
  3. Check for cross-area recycles (e.g., regenerated solvent returning to absorber)
  4. Check if any area matches a pre-built module
  5. Choose architecture:
FindingArchitectureOutput
≤ 15 units, single area or simple branchingA: Single ProcessSystemJSON
Standard subsystem (TEG, CO2 removal, etc.)B: ProcessModuleBaseClassPython code using pre-built module
Multiple areas, > 15 units, or cross-area recyclesC: ProcessModulePython code composing ProcessSystems
Giant facility (> 50 units) or nested recyclesC: Nested ProcessModulesPython code with nested modules

Important: When Architecture B or C is chosen, the extraction agent MUST:

  • Still extract all parameters using the same skill rules (units, component names, etc.)
  • Produce Python code instead of (or in addition to) JSON
  • Clearly state why JSON alone is insufficient
  • Use ProcessSystem.fromJsonAndRun() for individual sub-systems where possible, then compose them in Python

Hybrid Approach (JSON + Python Composition)

For large processes, use JSON for each sub-system and compose in Python:

import json
from neqsim import jneqsim

ProcessSystem = jneqsim.process.processmodel.ProcessSystem
ProcessModule = jneqsim.process.processmodel.ProcessModule

# Each plant area as JSON → ProcessSystem
sep_json = json.dumps({"fluid": {...}, "process": [...]})
comp_json = json.dumps({"fluid": {...}, "process": [...]})

sep_result = ProcessSystem.fromJson(sep_json)   # build but don't run yet
sep_process = sep_result.getProcessSystem()

comp_result = ProcessSystem.fromJson(comp_json)
comp_process = comp_result.getProcessSystem()

# Wire cross-system streams
# Get output from separation and set as input to compression
sep_gas = sep_process.getUnit("HP separator").getGasOutStream()
comp_feed = comp_process.getUnit("comp feed")  # The Stream in comp_json
# Replace comp feed's fluid with sep gas's fluid
comp_feed.setThermoSystem(sep_gas.getThermoSystem().clone())

# Compose and run
plant = ProcessModule("plant")
plant.add(sep_process)
plant.add(comp_process)
plant.run()

When NOT to Use Modules

  • Simple processes — don't over-architect a 4-unit process with modules
  • Learning / demos — use single ProcessSystem for clarity
  • JSON-only requirement — modules require Python/Java code
  • No cross-area interaction — independent systems can just be separate fromJsonAndRun() calls

17. Word Document (.docx) Input Support

Process descriptions often arrive as Word documents. This section covers extracting simulation parameters from .docx files using python-docx.

Required Library

import docx  # pip install python-docx

Document Loading Pattern

import os, docx

DOC_PATHS = [
    os.path.join(os.path.dirname(globals().get("__vsc_ipynb_file__", "")),
                 "process_description.docx"),
    r"C:\Users\...\process_description.docx",
    "process_description.docx",  # fallback: current directory
]

doc = None
for path in DOC_PATHS:
    if path and os.path.exists(path):
        doc = docx.Document(path)
        break
if doc is None:
    raise FileNotFoundError("Could not find document")

Extracting Paragraphs and Tables

paragraphs = [p.text.strip() for p in doc.paragraphs if p.text.strip()]
tables_data = []
for table in doc.tables:
    rows = [[cell.text.strip() for cell in row.cells] for row in table.rows]
    tables_data.append(rows)

Composition Extraction from Tables

Map document component names to NeqSim names using a lookup table:

NEQSIM_COMPONENT_MAP = {
    "Nitrogen": "nitrogen", "N2": "nitrogen",
    "CO2": "CO2", "Carbon Dioxide": "CO2",
    "Methane": "methane", "C1": "methane",
    "Ethane": "ethane", "C2": "ethane",
    "Propane": "propane", "C3": "propane",
    "i-Butane": "i-butane", "iC4": "i-butane",
    "n-Butane": "n-butane", "nC4": "n-butane",
    "i-Pentane": "i-pentane", "iC5": "i-pentane",
    "n-Pentane": "n-pentane", "nC5": "n-pentane",
    "C6+": "n-hexane",  # Simplified plus-fraction
    "n-Hexane": "n-hexane", "C6": "n-hexane",
    "H2S": "H2S", "Water": "water", "H2O": "water",
}

# Find the composition table (look for "Component" + "Mole fraction" headers)
composition = {}
for table_rows in tables_data:
    header = [h.lower() for h in table_rows[0]]
    if "component" in header and any("mole" in h or "fraction" in h for h in header):
        comp_col = header.index("component")
        frac_col = next(i for i, h in enumerate(header) if "mole" in h or "fraction" in h)
        for row in table_rows[1:]:
            doc_name = row[comp_col].strip()
            neqsim_name = NEQSIM_COMPONENT_MAP.get(doc_name)
            if neqsim_name:
                composition[neqsim_name] = float(row[frac_col])
        break

# Normalize to sum = 1.0
total = sum(composition.values())
composition = {k: v/total for k, v in composition.items()}

Operating Condition Extraction from Narrative

Extract temperatures and pressures from the prose text using pattern matching:

import re

def extract_from_paragraphs(paragraphs, patterns):
    """Search paragraphs for key-value patterns."""
    results = {}
    for p in paragraphs:
        for key, regex in patterns.items():
            m = re.search(regex, p, re.IGNORECASE)
            if m and key not in results:
                results[key] = float(m.group(1))
    return results

# Typical patterns found in process descriptions
patterns = {
    "feed_pressure":  r"(?:inlet|feed)\s+(?:pressure|P)\s*[:\s]+(\d+\.?\d*)\s*bar",
    "feed_temperature": r"(?:inlet|feed)\s+(?:temperature|T)\s*[:\s]+(\d+\.?\d*)\s*°?C",
    "hp_sep_pressure": r"HP\s+(?:separator|sep).*?(\d+\.?\d*)\s*bar",
    "export_pressure": r"export.*?(\d+\.?\d*)\s*bar",
}

For complex documents, also extract from tables with "Parameter"/"Value" structure:

for table_rows in tables_data:
    header = [h.lower() for h in table_rows[0]]
    if "parameter" in header and any("value" in h for h in header):
        param_col = header.index("parameter")
        val_col = next(i for i, h in enumerate(header) if "value" in h)
        for row in table_rows[1:]:
            param = row[param_col].lower()
            try:
                val = float(re.sub(r'[^\d.]', '', row[val_col]))
            except ValueError:
                continue
            if "pressure" in param:
                results["feed_pressure"] = val
            elif "temperature" in param:
                results["feed_temperature"] = val

Architecture Decision After Extraction

After extracting all parameters, count equipment and decide architecture:

# Count equipment mentioned in document
equipment_count = sum(1 for p in paragraphs
    if any(kw in p.lower() for kw in
           ["separator", "compressor", "cooler", "heater", "valve",
            "column", "pump", "mixer", "exchanger", "drum"]))

if equipment_count <= 8:
    print("Architecture A: Single ProcessSystem")
elif equipment_count <= 20:
    print("Architecture A or B: Single system or JSON builder")
else:
    print("Architecture C: ProcessModule with multiple sub-systems")

Tested Example

See examples/notebooks/process_extraction_from_document.ipynb for a full end-to-end example that demonstrates the Free Text → JSON → NeqSim Model pipeline:

  1. Loads a Word document describing a gas+condensate processing facility
  2. Extracts composition from a table (10 components)
  3. Extracts feed and operating conditions from tables and narrative
  4. Generates a NeqSim JSON file (process_from_document.json) as portable intermediate
  5. Builds process from JSON via ProcessSystem.fromJson() (core gas train, 10 units)
  6. Adds a scrubber liquid recycle using the hybrid approach (clone + Recycle object)
  7. Builds a full plant via ProcessModule with 3 sub-systems (20 units)
  8. Validates results (8/8 + 11/11 checks pass) and generates 3 figures

18. Recycle Stream Handling

When Recycles Are Needed

Recycles appear whenever a downstream liquid or gas stream must be returned to an upstream unit:

  • Scrubber liquid recycle: Knockout drum condensate → HP separator
  • Anti-surge recycle: Compressor discharge → compressor suction via cooler
  • Solvent loop: Regenerated solvent → top of absorber column
  • Reflux: Condenser liquid → top tray of distillation column

JSON Builder Limitation

The JSON builder (ProcessSystem.fromJson()) does not support recycle wiring natively. The Recycle class requires setOutletStream() which is not handled by the reflection-based inlet wiring in JsonProcessBuilder.

Solution: Hybrid approach — use JSON for the main process, then add recycles via Python/Java code after building from JSON.

Recycle Pattern (Clone + Recycle Object)

# 1. Clone a stream to create the "tear" (initial guess) stream
recycle_stream = source_stream.clone("Recycle Stream Name")
recycle_stream.setFlowRate(1e-6, "kg/hr")  # Tiny initial guess
process.add(recycle_stream)

# 2. Add the clone as an extra inlet to the receiving equipment
#    Use addStream() for separators/mixers
hp_separator.addStream(recycle_stream)

# 3. Build the rest of the process normally
# ... (knockout scrubber, pump, etc.)

# 4. Pump the actual recycle liquid back to upstream pressure
pump = ns.Pump("Recycle Pump", scrubber.getLiquidOutStream())
pump.setOutletPressure(upstream_pressure)
process.add(pump)

# 5. Create Recycle object to converge the loop
recycle = ns.Recycle("Recycle Name")
recycle.addStream(pump.getOutletStream())      # Actual downstream output
recycle.setOutletStream(recycle_stream)         # Clone tear stream
recycle.setTolerance(1e-2)                      # Flow/temp/comp tolerance
process.add(recycle)

# ProcessSystem.run() iterates until recycle converges
process.run()

Recycle API Reference

MethodDescription
Recycle(String name)Constructor
addStream(StreamInterface)Set the input (actual downstream stream)
setOutletStream(StreamInterface)Set the output (clone/tear stream)
setTolerance(double)Set flow/temp/composition convergence tolerance (default 1e-2)
setAccelerationMethod(AccelerationMethod)DIRECT_SUBSTITUTION, WEGSTEIN, BROYDEN
setMaxIterations(int)Max recycle iterations (default 10)

JSON Schema Extension for Recycles

When the extraction identifies a recycle, include it in the JSON process array as a comment/marker that signals hybrid wiring is needed:

{
  "type": "Recycle",
  "name": "Scrubber Liquid Recycle",
  "inlet": "Scrubber Pump",
  "properties": {
    "outletStream": "Recycle Stream Name",
    "tolerance": 0.01
  },
  "_note": "HYBRID: Requires Python wiring — JSON builder cannot set outletStream"
}

The agent should detect this marker and generate additional Python code for recycle wiring after the fromJson() call.

19. Three-Step Extraction Pipeline

The recommended workflow for converting free text to a running simulation:

┌─────────────┐     ┌──────────────┐     ┌──────────────────┐
│  Free Text   │ ──→ │  NeqSim JSON  │ ──→ │  Running Process  │
│  (.docx/txt) │     │  (.json file) │     │  (ProcessSystem)  │
└─────────────┘     └──────────────┘     └──────────────────┘
   Step 1: Parse       Step 2: Structure     Step 3: Build & Run

Step 1: Parse — Extract Parameters

  • Use python-docx for .docx, regex for plain text
  • Extract: composition, feed conditions, equipment list, operating conditions
  • Map component names using NEQSIM_COMPONENT_MAP (Section 5)
  • Normalize mole fractions to sum to 1.0

Step 2: Structure — Generate JSON

  • Build the JSON dict following the schema in Section 1
  • Use dot-notation for stream wiring (Section 3)
  • Save to a .json file for portability and version control
  • The JSON file is the single source of truth for the process

Step 3: Build & Run — Load JSON into NeqSim

# Load JSON and build process
import jpype
ProcessSystem = jpype.JClass("neqsim.process.processmodel.ProcessSystem")
result = ProcessSystem.fromJson(json_str)

if result.isError():
    for err in result.getErrors():
        print(f"[{err.getCode()}] {err.getMessage()}")
else:
    process = result.getProcessSystem()
    process.run()

    # Access equipment by name
    separator = process.getUnit("V-101 HP Separator")

    # Access streams via dot-notation
    gas_stream = process.resolveStreamReference("V-101 HP Separator.gasOut")

API Reference for JSON Pipeline

MethodReturnsDescription
ProcessSystem.fromJson(String)SimulationResultBuild process from JSON (no run)
ProcessSystem.fromJsonAndRun(String)SimulationResultBuild and run in one call
result.isError()booleanCheck for build errors
result.isSuccess()booleanCheck for success
result.getProcessSystem()ProcessSystemGet the built process
result.getErrors()List<ErrorDetail>Error details with codes and remediation
process.getUnit(String name)ProcessEquipmentInterfaceGet equipment by name
process.resolveStreamReference(String ref)StreamInterfaceGet stream by dot-notation

Stream Reference Dot-Notation

SuffixMethod CalledExample
.gasOut or .gasgetGasOutStream()"V-101 HP Sep.gasOut"
.liquidOut or .liquidgetLiquidOutStream()"V-201 Knockout.liquidOut"
.oilOut or .oilgetOilOutStream()"V-101 HP Sep.oilOut"
.waterOut or .watergetWaterOutStream()"V-101 HP Sep.waterOut"
.outlet (default)getOutletStream()"K-401 Compressor"

20. Compressor Performance Curves & Anti-Surge Handling

When to Add Compressor Curves

Add performance curves when the document mentions any of:

  • Compressor performance maps, characteristic curves, or operating envelopes
  • Surge protection, anti-surge control, or recycle valve
  • Compressor turndown requirements or minimum flow
  • Polytropic or isentropic head values
  • Multiple speed operations or variable speed drive (VSD)

CompressorChartGenerator — Auto-Generate from Design Point

CompressorChartGenerator creates a performance map (head, efficiency, surge/choke curves) scaled from the compressor's computed design point. The compressor must be run at least once first so the generator can read getPolytropicFluidHead(), getInletStream().getFlowRate("m3/hr"), and getSpeed().

import jpype
CompressorChartGenerator = jpype.JClass(
    "neqsim.process.equipment.compressor.CompressorChartGenerator"
)

# 1. Configure compressor
comp = ns.Compressor("K-401", inlet_stream)
comp.setUsePolytropicCalc(True)
comp.setOutletPressure(125.0)
comp.setPolytropicEfficiency(0.75)

# 2. Run process once to establish design point
process.add(comp)
process.run()  # Compressor now has head, flow, speed

# 3. Generate chart from design point
chart_gen = CompressorChartGenerator(comp)
comp.setCompressorChart(chart_gen.generateCompressorChart("mid range"))
comp.setCompressorChartType("interpolate and extrapolate")

# 4. Re-run with chart enabled
process.run()

Generation options:

MethodDescription
generateCompressorChart("normal")Single-speed, 5 points per curve
generateCompressorChart("mid range")Single-speed, 3 points (surge → stonewall)
generateCompressorChart("normal", 5)Multi-speed, 5 speed curves
generateFromTemplate("CENTRIFUGAL_STANDARD", 9)Standard centrifugal template
generateFromTemplate("CENTRIFUGAL_HIGH_FLOW", 5)High-flow template
generateFromTemplate("CENTRIFUGAL_HIGH_HEAD", 5)High-head template

Loading Manufacturer Curves from JSON

If the document includes actual performance data (from OEM data sheets):

# From a JSON file
comp.loadCompressorChartFromJson("path/to/chart.json")

# From a JSON string (e.g., extracted from document)
comp.loadCompressorChartFromJsonString(json_string)

# Export chart as JSON
chart_json = comp.getCompressorChartAsJson()

Chart JSON format:

{
  "compressorName": "K-401 Export Compressor",
  "headUnit": "kJ/kg",
  "maxDesignPower_kW": 8500.0,
  "referenceConditions": {
    "molecularWeight": 18.5,
    "temperature_K": 303.15,
    "pressure_bara": 65.0,
    "compressibilityZ": 0.92
  },
  "speedCurves": [
    {
      "speed_rpm": 3000.0,
      "flow_m3h": [3300, 4700, 6700],
      "head_kJkg": [88.8, 74.0, 37.0],
      "polytropicEfficiency_pct": [67.5, 75.0, 63.75]
    }
  ],
  "surgeFlow": [3300],
  "surgeHead": [88.8],
  "chokeFlow": [6700],
  "chokeHead": [37.0]
}

Anti-Surge Loop Topology

The typical NCS anti-surge pattern (used on offshore platforms):

                    ┌──────────────────────────────────────────┐
                    │              Anti-Surge Loop              │
                    ▼                                          │
Gas ──→ [Mixer] ──→ [Cooler] ──→ [Compressor w/Chart] ──→ [Splitter]──→ Main Export
                                                                │
                                                          [Calculator]
                                                                │
                                                     [Anti-Surge Valve] ──→ [Recycle]
                                                                               │
                                                                          back to Mixer

Equipment roles:

EquipmentRole
Clone streamInitial guess / tear stream for recycle (1e-6 kg/hr)
MixerCombines fresh feed + recycle at compressor suction
CoolerRemoves heat from recycle gas (optional but realistic)
CompressorWith chart enabled, tracks operating point vs surge line
SplitterSplits discharge into main product (stream 0) + recycle (stream 1)
CalculatorInput=Compressor, Output=Splitter; auto-calculates recycle flow
ThrottlingValveDrops recycle pressure back to suction pressure
RecycleConverges the loop (addStream → setOutletStream → tolerance)

Complete Anti-Surge Code Pattern

import jpype
CompressorChartGenerator = jpype.JClass(
    "neqsim.process.equipment.compressor.CompressorChartGenerator"
)
Calculator = jpype.JClass("neqsim.process.equipment.util.Calculator")

# 1. Clone for anti-surge recycle
as_recycle_stream = gas_stream.clone("AS Recycle Stream")
as_recycle_stream.setFlowRate(1e-6, "kg/hr")
process.add(as_recycle_stream)

# 2. Suction mixer
suction_mixer = ns.Mixer("Suction Mixer")
suction_mixer.addStream(gas_stream)
suction_mixer.addStream(as_recycle_stream)
process.add(suction_mixer)

# 3. Suction cooler (optional, removes recycle heat)
suction_cooler = ns.Cooler("Suction Cooler", suction_mixer.getOutletStream())
suction_cooler.setOutTemperature(273.15 + 30.0)
process.add(suction_cooler)

# 4. Compressor with polytropic calculation
comp = ns.Compressor("Export Compressor", suction_cooler.getOutletStream())
comp.setUsePolytropicCalc(True)
comp.setOutletPressure(125.0)
comp.setPolytropicEfficiency(0.75)
process.add(comp)

# 5. Discharge splitter (stream 0 = product, stream 1 = recycle)
splitter = ns.Splitter("Discharge Splitter", comp.getOutletStream(), 2)
splitter.setFlowRates(jpype.JArray(jpype.JDouble)([-1.0, 1e-6]), "kg/hr")
process.add(splitter)

# 6. Calculator: auto-adjusts splitter based on compressor state
calc = Calculator("Anti-Surge Calculator")
calc.addInputVariable(comp)
calc.setOutputVariable(splitter)
process.add(calc)

# 7. Anti-surge valve
valve = ns.ThrottlingValve("Anti-Surge Valve", splitter.getSplitStream(1))
valve.setOutletPressure(65.0)  # Match suction pressure
process.add(valve)

# 8. Recycle converges the loop
recycle = ns.Recycle("Anti-Surge Recycle")
recycle.addStream(valve.getOutletStream())
recycle.setOutletStream(as_recycle_stream)
recycle.setTolerance(1e-2)
process.add(recycle)

# 9. Export cooler on main product
cooler = ns.Cooler("Export Cooler", splitter.getSplitStream(0))
cooler.setOutTemperature(273.15 + 40.0)
process.add(cooler)

# Phase 1: Run without chart (establish design point)
process.run()

# Phase 2: Generate chart and re-run
chart_gen = CompressorChartGenerator(comp)
comp.setCompressorChart(chart_gen.generateCompressorChart("mid range"))
comp.setCompressorChartType("interpolate and extrapolate")
process.run()

# Check results
recycle_flow = splitter.getSplitStream(1).getFlowRate("kg/hr")
if recycle_flow > 1.0:
    print(f"Anti-surge active: {recycle_flow:.0f} kg/hr recycled")
else:
    print("Operating above surge line — no recycle needed")

Built-in AntiSurge Object (Simpler Alternative)

For simpler checks without the full recycle topology:

# Access the compressor's built-in anti-surge object
anti_surge = comp.getAntiSurge()
anti_surge.setActive(True)
anti_surge.setSurgeControlFactor(1.05)  # 5% margin above surge line

# After running:
is_surge = comp.isSurge()         # True if operating below surge line
is_stonewall = comp.isStoneWall() # True if at choke
head = comp.getPolytropicFluidHead()  # kJ/kg

AntiSurge control strategies:

StrategyDescription
ON_OFFBinary: valve fully open or closed at surge
PROPORTIONALValve opening proportional to distance from surge
PIDFull PID control (Kp=2.0, Ki=0.5, Kd=0.1 defaults)
PREDICTIVEAnticipates surge based on rate of change
DUAL_LOOPCombines flow controller + backup pressure controller

JSON Builder Limitation

Compressor curves and anti-surge loops cannot be configured via the JSON builder alone. Like recycles (Section 18), they require the hybrid approach: JSON for the main process topology, then Python/Java code to add:

  1. Performance chart (CompressorChartGenerator or loadFromJson)
  2. Anti-surge loop equipment (Mixer, Splitter, Calculator, Valve, Recycle)

Extraction Agent Detection Rules

When extracting from documents, flag compressors for anti-surge setup when:

  • Document mentions "anti-surge", "surge control", "recycle valve", or "minimum flow"
  • Compressor has turndown requirements (< 70% of design flow operation expected)
  • Multiple compressors in series (cascade surge risk)
  • Variable feed conditions (composition or flow rate swings)
  • Export/pipeline compressors (typically have anti-surge systems)

21. Tested Pitfalls & Known Limitations

Systematic testing of the extraction workflow (see examples/notebooks/test_extraction_workflow.ipynb) identified these pitfalls with fixes:

Component Name Mapping (CRITICAL)

NeqSim uses specific component names in its database. Verbose names from documents must be mapped to NeqSim names before generating JSON. The fromJson() builder does NOT do automatic alias resolution.

Document NameNeqSim NameCommon Mistake
carbon dioxideCO2"carbon dioxide" fails
hydrogen sulfide, hydrogen sulphideH2S"hydrogen sulfide" fails
water, H2OwaterOK
iso-butane, isobutanei-butane"iso-butane" may fail
iso-pentane, isopentanei-pentane"iso-pentane" may fail
monoethylene glycolMEG"monoethylene glycol" fails
triethylene glycolTEG"triethylene glycol" fails
methyl diethanolamineMDEAVerbose form fails

Rule: Always map verbose component names to the short NeqSim database names listed in Section 5 before generating the JSON.

CPA Mixing Rule Name (CRITICAL)

The mixing rule for CPA must be the enum name "CLASSIC_TX_CPA", not the numeric string "10". The setMixingRule(String) method calls EosMixingRuleType.byName() which expects uppercase enum names.

CorrectWrongError
"mixingRule": "CLASSIC_TX_CPA""mixingRule": "10"EosMixingRuleType:byName - Input name is not valid
"mixingRule": "classic""mixingRule": "2"Same error

Mixer Multi-Inlet Syntax (CRITICAL)

Mixers require "inlets" (plural key) with an array value. Using "inlet" with an array causes "Array must have size 1" error.

CorrectWrong
"inlets": ["stream1", "stream2"]"inlet": ["stream1", "stream2"]

Separator Stream Resolution

resolveStreamReference("HP Sep") returns null for separators — you MUST specify the port. For non-separator equipment (Cooler, Compressor, etc.), bare names work fine.

EquipmentBare NameWith Port
Separatorreturns null"HP Sep.gasOut" or "HP Sep.liquidOut"
ThreePhaseSeparatorreturns null"Sep.gasOut", "Sep.oilOut", "Sep.waterOut"
Cooler"Cooler" works"Cooler.outlet" also works
Compressor"Comp" works"Comp.outlet" also works
Stream"feed" worksN/A

Empty Composition Silently Passes

An empty "components": {} does not error at build time but produces a system with no components. Always validate len(components) > 0 before generating JSON.

Unsupported Equipment Types

The following equipment types are NOT supported by fromJson():

  • Absorber — use Separator chain or hybrid approach
  • DistillationColumn — use hybrid approach (build column in Python/Java)
  • Reactor — use hybrid approach with GibbsReactor in Python/Java
  • Filter — not in JSON builder

When these are detected in a document, flag them as requiring hybrid approach and build the surrounding process in JSON, leaving these for manual wiring.

Error Message Actionability

The fromJson() error messages are structured and actionable:

  • [FLUID_ERROR] → Check component names and mixing rule
  • [UNIT_ERROR] → Check equipment type name and properties
  • [STREAM_NOT_FOUND] → Check inlet reference matches a previously defined unit
  • [NO_FLUID] → The fluid failed to create, cascading to dependent units

Test Results Summary (14 tests)

CategoryTestsPassedKey Findings
Main Scenarios55/53-stage compression, TEG dehydration, subsea tieback, tabular HMB, ambiguous input all pass
Edge Cases (original)61/6Verbose names, wrong mixer syntax, wrong CPA rule, invalid type, missing ref all caught with clear errors
Edge Cases (corrected)33/3Fixes for CPA rule, mixer syntax, component names all verified

See examples/notebooks/test_extraction_workflow.ipynb for the full test suite.

Frequently asked questions

What to verify before installation and use

What does the neqsim-process-extraction source document cover?

Convert unstructured engineering information into the canonical NeqSim JSON format accepted by ProcessSystem.fromJson() and ProcessSystem.fromJsonAndRun().

How do I install neqsim-process-extraction?

The source record exposes this install command: npx skills add https://github.com/equinor/neqsim --skill ".github/skills/neqsim-process-extraction". Inspect the command and pinned source before running it.

Which permission-related actions were detected?

Static rules flagged write-files in the source; the page lists the matching lines and excerpts.

Alternatives

Compare before choosing

Computed 10014,706

prowler-cloud/prowler

postgresql-indexing

PostgreSQL indexing best practices for Prowler: index design, partial indexes, partitioned table indexing, EXPLAIN ANALYZE validation, concurrent operations, monitoring, and maintenance. Trigger: When creating or modifying PostgreSQL indexes, analyzing query performance with EXPLAIN, debugging slow queries, reviewing index usage statistics, reindexing, dropping indexes, or working with partitioned table indexes. Also trigger when discussing index strategies, partial indexes, or index maintenance

Computed 100147

oaustegard/claude-skills

featuring

Generate hierarchical _FEATURES.md files that describe what a codebase DOES from a user/consumer perspective, anchored to source symbols via tree-sitting. Supports large complex codebases through feature-driven decomposition into sub-feature files. Uses a multi-pass synthesis: orientation → detail → overview rewrite. Use when someone says "what does this do", "document features", "feature inventory", "_FEATURES.md", or needs to understand a codebase's purpose before modifying it. Complements tre

Computed 9931,947

HKUDS/Vibe-Trading

strategy-generate

Create, modify, and optimize quantitative trading strategies, then backtest and evaluate them.

Computed 9982

vasilyu1983/AI-Agents-public

qa-testing-ios

Guides iOS testing with XCTest, XCUITest, Swift Testing, simctl, and xcresult. Use when choosing destinations, controlling flakes, or parsing test artifacts for native apps.