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.
equinor/neqsim/.github/skills/neqsim-process-extraction/SKILL.md
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.
Decision brief
Convert unstructured engineering information into the canonical NeqSim JSON format accepted by ProcessSystem.fromJson() and ProcessSystem.fromJsonAndRun().
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/equinor/neqsim --skill ".github/skills/neqsim-process-extraction"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
Follow this step-by-step process for every extraction:
Text description — paragraph or bullet list describing a process
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…
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
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:…
Permission review
The documentation asks the agent to create, modify, or delete local files.
Save to a `.json` file for portability and version controlEvidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 96/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 147 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Convert unstructured engineering information into the canonical NeqSim JSON format
accepted by ProcessSystem.fromJson() and ProcessSystem.fromJsonAndRun().
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.PipingRouteBuilderrather 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 multipleProcessSystemobjects composed inside aProcessModule, or use pre-builtProcessModuleBaseClassimplementations. See Section 16 for the decision guide.
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 | Type | Required | Description |
|---|---|---|---|
fluid.model | string | Yes | EOS model: SRK, PR, CPA, GERG2008, PCSAFT, UMRPRU |
fluid.temperature | number | Yes | Temperature in Kelvin |
fluid.pressure | number | Yes | Pressure in bara |
fluid.mixingRule | string | Yes | Usually "classic" for SRK/PR, "CLASSIC_TX_CPA" for CPA |
fluid.multiPhaseCheck | boolean | No | Set true for water+HC or 3-phase systems |
fluid.components | object | Yes | Component name → mole fraction (must sum to ~1.0) |
process[].type | string | Yes | Equipment type from the Equipment Type Table below |
process[].name | string | Yes | Unique equipment tag / display name |
process[].inlet | string | Conditional | Stream reference (dot-notation). Required for all except 1st Stream |
process[].properties | object | No | Equipment-specific settings (see Properties Reference) |
autoRun | boolean | No | Set true to auto-run after building |
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"]}}
]
}
Map natural language equipment names to NeqSim JSON type values.
Use the longest matching keyword to avoid false matches.
| Natural Language Synonyms | NeqSim 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 scrubber | Separator |
| 3-phase separator, three-phase separator, production separator, test separator, oil-water-gas separator, 3-phase test separator | ThreePhaseSeparator |
| Natural Language Synonyms | NeqSim 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 compressor | Compressor |
| expander, turbo-expander, turboexpander, power recovery turbine | Expander |
| Natural Language Synonyms | NeqSim type |
|---|---|
| cooler, gas cooler, aftercooler, after-cooler, intercooler, air cooler, fin fan cooler, air-fin cooler, trim cooler, export cooler, overhead condenser | Cooler |
| heater, pre-heater, preheater, line heater, electric heater, fired heater, reboiler, trim heater | Heater |
| heat exchanger, shell and tube, shell-and-tube, plate heat exchanger, plate-fin exchanger, FWHE, gas-gas exchanger, cross-exchanger, economizer | HeatExchanger |
| Natural Language Synonyms | NeqSim 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 choke | ThrottlingValve |
| Natural Language Synonyms | NeqSim type |
|---|---|
| pump, centrifugal pump, export pump, booster pump, injection pump, feed pump, charge pump, transfer pump, multiphase pump | Pump |
| Natural Language Synonyms | NeqSim target |
|---|---|
| line list, line-list, route table, STID route, E3D route, stress isometric, pipe run list, serial piping route, compressor suction route, compressor discharge route | PipingRouteBuilder |
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.
| Natural Language Synonyms | NeqSim type |
|---|---|
| mixer, mixing tee, junction, merge, combine | Mixer |
| splitter, tee, flow divider, bypass tee | Splitter |
| manifold, production manifold, gathering manifold, commingling manifold, subsea manifold, inlet/export header | Manifold |
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.
| Natural Language Synonyms | NeqSim type |
|---|---|
| stream, feed, inlet, well stream, feed gas, feed stream, input, source | Stream |
| Natural Language Synonyms | NeqSim type |
|---|---|
| tank, storage tank, atmospheric tank, settling tank, buffer tank | Tank |
| flare, flare stack, flare header, HP flare, LP flare | Flare |
| recycle, recirculation | Recycle |
| ejector, jet pump, steam ejector, gas ejector | Ejector |
| TEG absorber, glycol contactor, TEG contactor, dehydration absorber | SimpleTEGAbsorber |
| reservoir, simple reservoir | SimpleReservoir |
| electrolyzer, water electrolyzer, PEM electrolyzer | Electrolyzer |
| CO2 electrolyzer | CO2Electrolyzer |
| fuel cell | FuelCell |
| wind turbine | WindTurbine |
| solar panel, PV panel | SolarPanel |
| battery storage, battery, BESS | BatteryStorage |
| ammonia reactor, Haber-Bosch reactor, ammonia synthesis | AmmoniaSynthesisReactor |
| distillation column, fractionation column, distillation tower, deethanizer, demethanizer, depropanizer, debutanizer, stripper column, stabilizer column | DistillationColumn |
| pipe, pipe segment, pipeline, flowline, adiabatic pipe | AdiabaticPipe |
| stream saturator, saturator, water saturator | StreamSaturatorUtil |
Equipment is connected via dot-notation references in the inlet field.
| Upstream Equipment Type | Port Syntax | Resolves 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 |
Stream — it gets the fluid from the fluid sectioninlet reference pointing to a previously defined equipmentgasOut, liquidOut, oilOut, waterOut"name.outlet" or just "name" (default resolves to outlet)Recycle equipment for convergence)"inlets" (plural) — Mixers require "inlets": ["stream1", "stream2"] (array). Do NOT use "inlet" with an array — it will fail with "Array must have size 1"resolveStreamReference("HP Sep") returns null. Always use "HP Sep.gasOut" or "HP Sep.liquidOut"{"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", ...}
{"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.
| Property | Type | Unit | Example |
|---|---|---|---|
flowRate | [number, "unit"] | kg/hr, MSm3/day, Am3/hr | [75000.0, "kg/hr"] |
temperature | number | Kelvin | 353.15 (= 80°C) |
pressure | number | bara | 65.0 |
| Property | Type | Default | Description |
|---|---|---|---|
outletPressure | number (bara) | — | Discharge pressure |
isentropicEfficiency | number (0-1) | 0.75 | Isentropic efficiency |
polytropicEfficiency | number (0-1) | — | Polytropic efficiency (alternative) |
usePolytropicCalc | boolean | false | Use polytropic head calculation |
| Property | Type | Default | Description |
|---|---|---|---|
outTemperature | number (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.
| Property | Type | Default | Description |
|---|---|---|---|
outletPressure | number (bara) | — | Downstream pressure |
| Property | Type | Default | Description |
|---|---|---|---|
outletPressure | number (bara) | — | Discharge pressure |
isentropicEfficiency | number (0-1) | 0.75 | Isentropic efficiency |
No required properties. Operates at inlet conditions.
| Property | Type | Default | Description |
|---|---|---|---|
splitNumber | integer | — | Number of outlet streams |
splitFactors | [number, ...] | — | Split factors per outlet (e.g., [0.5, 0.5]) |
| Property | Type | Default | Description |
|---|---|---|---|
numberOfTrays | integer | 10 | Number of theoretical trays |
hasReboiler | boolean | true | Whether column has a reboiler |
hasCondenser | boolean | true | Whether column has a condenser |
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).
| Property | Type | Default | Description |
|---|---|---|---|
length | number (m) | — | Pipe length in meters |
diameter | number (m) | — | Pipe inner diameter in meters |
When the source has a line-list or stress-isometric table, extract these fields before constructing the route:
| Extracted field | Required | Notes |
|---|---|---|
segment_id | Yes | Line number, row id, or generated S1, S2 |
from_node, to_node | Yes | Equipment tag, nozzle, tee, manifold, or route node |
length, length_unit | Yes | Straight pipe length, not equivalent length |
internal_diameter, diameter_unit | Yes | Convert NPS/schedule to internal diameter first |
wall_thickness, wall_thickness_unit | No | Store if schedule or stress iso gives it |
elevation_change, elevation_unit | No | Positive uphill, negative downhill |
roughness, roughness_unit | No | Use default roughness when only piping class is known |
minor_losses | No | Fittings/valves as {type, k_value} rows |
source_ref | Yes | Drawing/page/row reference for traceability |
Route extraction workflow:
addSegment(...).PipingRouteBuilder.build(feedStream)
and run the returned ProcessSystem.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.route.toJson() and pressure-drop results in the task folder.Reference guide: docs/process/piping_route_builder.md.
Map common aliases to NeqSim database names. The NeqSim name is case-sensitive.
| Common Aliases | NeqSim Name |
|---|---|
| C1, CH4, methane | methane |
| C2, C2H6, ethane | ethane |
| C3, C3H8, propane | propane |
| iC4, i-C4, isobutane | i-butane |
| nC4, n-C4, butane, normal-butane | n-butane |
| iC5, i-C5, isopentane | i-pentane |
| nC5, n-C5, pentane, normal-pentane | n-pentane |
| nC6, n-C6, hexane | n-hexane |
| nC7, n-C7, heptane | n-heptane |
| nC8, n-C8, octane | n-octane |
| nC9, n-C9, nonane | n-nonane |
| nC10, n-C10, decane | nC10 |
| nC11 through nC24 | nC11 through nC24 |
| Common Aliases | NeqSim Name |
|---|---|
| CO2, carbon dioxide | CO2 |
| H2S, hydrogen sulfide, hydrogen sulphide | H2S |
| N2, nitrogen | nitrogen |
| H2, hydrogen | hydrogen |
| O2, oxygen | oxygen |
| Ar, argon | argon |
| He, helium | helium |
| H2O, water | water |
| Hg, mercury | mercury |
| COS, carbonyl sulfide | COS |
| SO2, sulfur dioxide | SO2 |
| Common Aliases | NeqSim Name |
|---|---|
| MEG, monoethylene glycol, ethylene glycol | MEG |
| DEG, diethylene glycol | DEG |
| TEG, triethylene glycol | TEG |
| MeOH, methanol | methanol |
| EtOH, ethanol | ethanol |
| MDEA, methyldiethanolamine | MDEA |
| Common Aliases | NeqSim Name |
|---|---|
| benzene, C6H6 | benzene |
| toluene, C7H8, methylbenzene | toluene |
| cyclohexane, c-C6, cy-C6 | c-hexane |
| cyclopentane, c-C5, cy-C5 | c-C5 |
All NeqSim JSON values must be in standard units. Convert before inserting into JSON.
| Input Unit | To Kelvin | Formula |
|---|---|---|
| °C, degC, Celsius | K | T_K = T_C + 273.15 |
| °F, degF, Fahrenheit | K | T_K = (T_F - 32) × 5/9 + 273.15 |
| K, Kelvin | K | Identity |
| °R, Rankine | K | T_K = T_R × 5/9 |
| Input Unit | To bara | Formula |
|---|---|---|
| barg, bar gauge | bara | P_bara = P_barg + 1.01325 |
| bara, bar absolute | bara | Identity |
| psia, psi absolute | bara | P_bara = P_psia × 0.0689476 |
| psig, psi gauge | bara | P_bara = (P_psig + 14.696) × 0.0689476 |
| kPa, kilopascal | bara | P_bara = P_kPa / 100.0 |
| MPa, megapascal | bara | P_bara = P_MPa × 10.0 |
| atm, atmosphere | bara | P_bara = P_atm × 1.01325 |
Flow rate in JSON uses the [value, "unit"] array format. Supported unit strings:
| Unit String | Description |
|---|---|
"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 |
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):
| Component | NeqSim Name | $M$ (g/mol) |
|---|---|---|
| Methane | methane | 16.04 |
| Ethane | ethane | 30.07 |
| Propane | propane | 44.10 |
| n-Butane | n-butane | 58.12 |
| i-Butane | i-butane | 58.12 |
| n-Pentane | n-pentane | 72.15 |
| n-Hexane | n-hexane | 86.18 |
| CO2 | CO2 | 44.01 |
| H2S | H2S | 34.08 |
| Nitrogen | nitrogen | 28.01 |
| Water | water | 18.02 |
| MEG | MEG | 62.07 |
| TEG | TEG | 150.17 |
| MDEA | MDEA | 119.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) |
|---|---|---|---|---|
| methane | 0.70 | 16.04 | 0.04364 | 0.8370 |
| ethane | 0.20 | 30.07 | 0.00665 | 0.1275 |
| propane | 0.10 | 44.10 | 0.00227 | 0.0355 |
| Total | 1.00 | 0.05216 | 1.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.
Choose the thermodynamic model based on the fluid system:
| Fluid System | Recommended Model | Mixing Rule |
|---|---|---|
| Dry gas, lean gas, simple hydrocarbons | SRK | "classic" |
| Oil systems, general hydrocarbons | PR | "classic" |
| Water + hydrocarbons, MEG/methanol, polar | CPA | "CLASSIC_TX_CPA" |
| Fiscal metering, custody transfer | GERG2008 | (none needed) |
| Polymer/associating fluids | PCSAFT | "classic" |
CPA with mixing rule "CLASSIC_TX_CPA" and set multiPhaseCheck: trueGERG2008PR or SRK with "classic" mixing ruleSRK with "classic" mixing ruleFollow this step-by-step process for every extraction:
type for each"equipment_name.port"bara, barg, bar, psi, MPa, kPa, atm°C, °F, K, degC, degFkg/hr, t/h, MMSCFD, MSm3/d, Am3/hrfluid section with model, T, P, mixing rule, and componentsprocess array in topological order (upstream before downstream)Stream with the feed fluidinlet referencesautoRun: trueScore the extraction confidence on a 0.0–1.0 scale:
| Criterion | Points |
|---|---|
| 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 |
| Score | Label | Recommendation |
|---|---|---|
| 0.80–1.00 | High | Run directly, review results |
| 0.60–0.79 | Medium | Run but flag assumptions for user review |
| 0.40–0.59 | Low | More information needed — show what's missing |
| 0.00–0.39 | Very Low | Cannot produce reliable simulation — ask user |
When information is not specified, use these engineering defaults and always track them:
| Parameter | Default Value | Assumption Text |
|---|---|---|
| EOS model | SRK | "Default SRK EOS (not specified in source)" |
| Mixing rule | classic | "Classic mixing rule assumed" |
| Feed temperature | 288.15 K (15°C) | "Standard temperature assumed (15°C)" |
| Feed pressure | 1.01325 bara | "Atmospheric pressure assumed" |
| Feed flow rate | 50000 kg/hr | "Default flow rate 50000 kg/hr assumed" |
| Compressor efficiency | 0.75 (isentropic) | "Default isentropic efficiency 0.75 assumed" |
| Cooler outlet temp | 308.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" |
When the extracted topology matches a known pattern, use a template for better reliability.
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
}
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
}
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^iCompressor → Cooler (to intercooler temp) → Separator (scrub condensate)Compressor only (no aftercooler/scrubber, unless specified)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
}
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
}
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
}
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%.
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.
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.
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.
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:
| Step | Extracted |
|---|---|
| Composition | NOT PROVIDED → flag as missing, use placeholder |
| Feed T | 40°C → 313.15 K |
| Feed P | 80 bara → 80.0 |
| Cooler | Target 15°C → outTemperature: 288.15 |
| Separator | After cooler, takes gas port |
| Compressor | 120 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:
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:
Input Table:
| Stream | Phase | T (°C) | P (barg) | Flow (t/h) | CH4 mol% | C2H6 mol% | C3H8 mol% | CO2 mol% |
|---|---|---|---|---|---|---|---|---|
| Feed | V+L | 60 | 48 | 120 | 82.5 | 7.2 | 4.1 | 2.3 |
Equipment List: HP separator → export compressor (95 barg) → aftercooler (35°C) → scrubber
Extraction:
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:
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
}
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:
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
}
Every extraction MUST produce three things:
The complete JSON object ready for ProcessSystem.fromJsonAndRun().
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]
...
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 —
If ProcessSystem.fromJsonAndRun() returns errors, interpret them:
| Error Code | Meaning | Likely Fix |
|---|---|---|
JSON_PARSE_ERROR | Malformed JSON | Check JSON syntax (missing comma, unmatched brace) |
MISSING_PROCESS | No process array | Add the process equipment array |
MISSING_TYPE | Equipment has no type | Add "type": "..." to the unit definition |
STREAM_NOT_FOUND | Inlet reference points to nonexistent equipment | Check equipment order and name spelling |
FLUID_NOT_FOUND | fluidRef points to undefined fluid | Define the fluid in the fluids section |
NO_FLUID | Stream has no fluid | Define a fluid section or add fluidRef |
UNKNOWN_MODEL | Unrecognized EOS model | Use: SRK, PR, CPA, GERG2008, PCSAFT, UMRPRU |
UNIT_ERROR | Equipment creation failed | Check property values and types |
SIMULATION_ERROR | Runtime failure during process.run() | Check pressure/temperature ranges, compositions |
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()}")
Patterns for converting operating data from external sources (Excel, CSV, historians) into NeqSim JSON.
When operating data arrives as a table (common in FEED reports, well tests, plant data):
Step 1 — Identify columns:
| Column Type | Maps 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}
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
}
| Issue | Detection | Fix |
|---|---|---|
| Composition sums to ~100 not ~1 | Sum > 1.5 | Divide all values by 100 |
| Composition sums to < 0.95 | Sum < 0.95 | Assign remainder to heaviest component, flag assumption |
| Pressure in barg not bara | Values look low (< 1 for HP systems) | Add 1.01325 to convert |
| Temperature in °C not K | Values < 200 for process | Add 273.15 |
| Missing flow units | No unit column | Default to kg/hr, flag assumption |
| ppm trace components | Values > 100 in composition column | Convert: mol_frac = ppm × 1e-6 |
NeqSim has three levels of process model organization. The extraction agent MUST choose the right level before assembling JSON or Python code.
| Process Complexity | Units | Recycles | Architecture | JSON Support |
|---|---|---|---|---|
| Simple / linear | 1–8 | 0–1 | Single ProcessSystem | YES — use fromJsonAndRun() |
| Medium | 5–15 | 0–1 | Single ProcessSystem | YES — use fromJsonAndRun() |
| Large / multi-area | 10–50+ | 0–3+ | ProcessModule composing multiple ProcessSystems | NO — must use Python/Java code |
| Reusable subsystem | any | internal | ProcessModuleBaseClass subclass | NO — must use Python/Java code |
Decision rules (apply in order):
ProcessSystem via JSON.ProcessSystem via JSON (recycles supported via Recycle equipment type).ProcessSystem objects inside a ProcessModule.ProcessModuleBaseClass subclass.ProcessModule containing other ProcessModules.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:
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 Class | Purpose | Input Streams | Output Streams |
|---|---|---|---|
SeparationTrainModule | Multi-stage HP/MP/LP separation | "feed stream" | "gas exit stream", "oil exit stream" |
SeparationTrainModuleSimple | Simplified 2-stage separation | "feed stream" | "gas exit stream", "oil exit stream" |
GlycolDehydrationlModule | TEG dehydration (absorber + stripper + regen) | "gasStreamToAbsorber", "strippingGas" | "gasStreamFromAbsorber", "liquidFromStripper" |
CO2RemovalModule | CO2 absorption/stripping | "streamToAbsorber" | "streamFromAbsorber" |
AdsorptionDehydrationlModule | Adsorption dehydration with multiple beds | "gasStreamToAdsorber" | "gasStreamFromAdsorber" |
DPCUModule | Dew Point Control Unit (expander + column) | "feed stream" | "gas exit stream", "oil exit stream" |
PropaneCoolingModule | Propane refrigeration cycle | "refrigerant" | "refrigerant" |
MEGReclaimerModule | MEG reclamation | "streamToReclaimer" | "streamToWaterRemoval" |
MixerGasProcessingModule | Gas processing with glycol injection | "feed stream", "glycol feed stream" | "gas exit stream", "oil exit stream" |
WellFluidModule | Well 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 namegetOutputStream(String name) — get output by port namegetOperations() → ProcessSystem — access the internal processgetUnit(String name) — access individual equipment inside the moduleinitializeModule() — called automatically when added to a ProcessSystemWhen 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:
| Method | Description |
|---|---|
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).
When extracting from a source document, add a Step 0 before the normal extraction:
Step 0: Classify Process Complexity
| Finding | Architecture | Output |
|---|---|---|
| ≤ 15 units, single area or simple branching | A: Single ProcessSystem | JSON |
| Standard subsystem (TEG, CO2 removal, etc.) | B: ProcessModuleBaseClass | Python code using pre-built module |
| Multiple areas, > 15 units, or cross-area recycles | C: ProcessModule | Python code composing ProcessSystems |
| Giant facility (> 50 units) or nested recycles | C: Nested ProcessModules | Python code with nested modules |
Important: When Architecture B or C is chosen, the extraction agent MUST:
ProcessSystem.fromJsonAndRun() for individual sub-systems where possible,
then compose them in PythonFor 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()
fromJsonAndRun() callsProcess descriptions often arrive as Word documents. This section covers extracting
simulation parameters from .docx files using python-docx.
import docx # pip install python-docx
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")
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)
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()}
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
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")
See examples/notebooks/process_extraction_from_document.ipynb for a full
end-to-end example that demonstrates the Free Text → JSON → NeqSim Model pipeline:
process_from_document.json) as portable intermediateProcessSystem.fromJson() (core gas train, 10 units)Recycles appear whenever a downstream liquid or gas stream must be returned to an upstream unit:
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.
# 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()
| Method | Description |
|---|---|
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) |
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.
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
python-docx for .docx, regex for plain textNEQSIM_COMPONENT_MAP (Section 5).json file for portability and version control# 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")
| Method | Returns | Description |
|---|---|---|
ProcessSystem.fromJson(String) | SimulationResult | Build process from JSON (no run) |
ProcessSystem.fromJsonAndRun(String) | SimulationResult | Build and run in one call |
result.isError() | boolean | Check for build errors |
result.isSuccess() | boolean | Check for success |
result.getProcessSystem() | ProcessSystem | Get the built process |
result.getErrors() | List<ErrorDetail> | Error details with codes and remediation |
process.getUnit(String name) | ProcessEquipmentInterface | Get equipment by name |
process.resolveStreamReference(String ref) | StreamInterface | Get stream by dot-notation |
| Suffix | Method Called | Example |
|---|---|---|
.gasOut or .gas | getGasOutStream() | "V-101 HP Sep.gasOut" |
.liquidOut or .liquid | getLiquidOutStream() | "V-201 Knockout.liquidOut" |
.oilOut or .oil | getOilOutStream() | "V-101 HP Sep.oilOut" |
.waterOut or .water | getWaterOutStream() | "V-101 HP Sep.waterOut" |
.outlet (default) | getOutletStream() | "K-401 Compressor" |
Add performance curves when the document mentions any of:
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:
| Method | Description |
|---|---|
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 |
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]
}
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:
| Equipment | Role |
|---|---|
| Clone stream | Initial guess / tear stream for recycle (1e-6 kg/hr) |
| Mixer | Combines fresh feed + recycle at compressor suction |
| Cooler | Removes heat from recycle gas (optional but realistic) |
| Compressor | With chart enabled, tracks operating point vs surge line |
| Splitter | Splits discharge into main product (stream 0) + recycle (stream 1) |
| Calculator | Input=Compressor, Output=Splitter; auto-calculates recycle flow |
| ThrottlingValve | Drops recycle pressure back to suction pressure |
| Recycle | Converges the loop (addStream → setOutletStream → tolerance) |
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")
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:
| Strategy | Description |
|---|---|
ON_OFF | Binary: valve fully open or closed at surge |
PROPORTIONAL | Valve opening proportional to distance from surge |
PID | Full PID control (Kp=2.0, Ki=0.5, Kd=0.1 defaults) |
PREDICTIVE | Anticipates surge based on rate of change |
DUAL_LOOP | Combines flow controller + backup pressure controller |
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:
When extracting from documents, flag compressors for anti-surge setup when:
Systematic testing of the extraction workflow (see examples/notebooks/test_extraction_workflow.ipynb)
identified these pitfalls with fixes:
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 Name | NeqSim Name | Common Mistake |
|---|---|---|
| carbon dioxide | CO2 | "carbon dioxide" fails |
| hydrogen sulfide, hydrogen sulphide | H2S | "hydrogen sulfide" fails |
| water, H2O | water | OK |
| iso-butane, isobutane | i-butane | "iso-butane" may fail |
| iso-pentane, isopentane | i-pentane | "iso-pentane" may fail |
| monoethylene glycol | MEG | "monoethylene glycol" fails |
| triethylene glycol | TEG | "triethylene glycol" fails |
| methyl diethanolamine | MDEA | Verbose form fails |
Rule: Always map verbose component names to the short NeqSim database names listed in Section 5 before generating the JSON.
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.
| Correct | Wrong | Error |
|---|---|---|
"mixingRule": "CLASSIC_TX_CPA" | "mixingRule": "10" | EosMixingRuleType:byName - Input name is not valid |
"mixingRule": "classic" | "mixingRule": "2" | Same error |
Mixers require "inlets" (plural key) with an array value. Using "inlet" with
an array causes "Array must have size 1" error.
| Correct | Wrong |
|---|---|
"inlets": ["stream1", "stream2"] | "inlet": ["stream1", "stream2"] |
resolveStreamReference("HP Sep") returns null for separators — you MUST specify
the port. For non-separator equipment (Cooler, Compressor, etc.), bare names work fine.
| Equipment | Bare Name | With Port |
|---|---|---|
Separator | returns null | "HP Sep.gasOut" or "HP Sep.liquidOut" |
ThreePhaseSeparator | returns null | "Sep.gasOut", "Sep.oilOut", "Sep.waterOut" |
Cooler | "Cooler" works | "Cooler.outlet" also works |
Compressor | "Comp" works | "Comp.outlet" also works |
Stream | "feed" works | N/A |
An empty "components": {} does not error at build time but produces a system with
no components. Always validate len(components) > 0 before generating JSON.
The following equipment types are NOT supported by fromJson():
Absorber — use Separator chain or hybrid approachDistillationColumn — use hybrid approach (build column in Python/Java)Reactor — use hybrid approach with GibbsReactor in Python/JavaFilter — not in JSON builderWhen these are detected in a document, flag them as requiring hybrid approach and build the surrounding process in JSON, leaving these for manual wiring.
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| Category | Tests | Passed | Key Findings |
|---|---|---|---|
| Main Scenarios | 5 | 5/5 | 3-stage compression, TEG dehydration, subsea tieback, tabular HMB, ambiguous input all pass |
| Edge Cases (original) | 6 | 1/6 | Verbose names, wrong mixer syntax, wrong CPA rule, invalid type, missing ref all caught with clear errors |
| Edge Cases (corrected) | 3 | 3/3 | Fixes for CPA rule, mixer syntax, component names all verified |
See examples/notebooks/test_extraction_workflow.ipynb for the full test suite.
Frequently asked questions
Convert unstructured engineering information into the canonical NeqSim JSON format accepted by ProcessSystem.fromJson() and ProcessSystem.fromJsonAndRun().
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.
Static rules flagged write-files in the source; the page lists the matching lines and excerpts.
Alternatives
prowler-cloud/prowler
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
oaustegard/claude-skills
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
HKUDS/Vibe-Trading
Create, modify, and optimize quantitative trading strategies, then backtest and evaluate them.
vasilyu1983/AI-Agents-public
Guides iOS testing with XCTest, XCUITest, Swift Testing, simctl, and xcresult. Use when choosing destinations, controlling flakes, or parsing test artifacts for native apps.