Skip to main content

neqsim-api-patterns

NeqSim API patterns and code recipes. USE WHEN: writing Java or Python code that uses NeqSim for thermodynamic calculations, process simulation, or property retrieval. Covers EOS selection, fluid creation, flash calculations, property access, equipment patterns, and unit conventions.

Source facts

Repository
equinor/neqsim
Last source activity
October 12, 2026 at 15:15
Detected SKILL.md language
English
Stars
156
Forks
73

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
neqsim-api-patterns
description
NeqSim API patterns and code recipes. USE WHEN: writing Java or Python code that uses NeqSim for thermodynamic calculations, process simulation, or property retrieval. Covers EOS selection, fluid creation, flash calculations, property access, equipment patterns, and unit conventions.
last_verified
2026-10-12
# NeqSim API Patterns Copy-paste reference for common NeqSim operations. All Java code must be Java 8 compatible. ## MCP server vs. Python/Java API — which to use **Default policy: MCP first.** For any single calculation, always check whether a NeqSim MCP tool (`mcp_neqsim_*`) already covers it before writing Python or Java. Only drop to code when MCP genuinely cannot do the job. Concretely: 1. **Curated tool exists** (`runFlash`, `runProcess`, `runPVT`, `getPhaseEnvelope`, `sizeEquipment`, `calculateStandard`, `runFlowAssurance`, `runBatch`, ...) → use it directly. Confirm field names with `getSchema`/`validateInput` first. 2. **No curated tool, but it might still be reachable** → use `runCapability` (`search` → `inspectApi` → invoke) before writing any code. 3. **`runCapability` reports `inspect-only`, or the task needs loops/plotting/ state/notebooks/reports** → fall back to the Python API (`import neqsim`) or Java in a checkout. This is the *only* reason to write code for a calculation MCP already exposes. 4. **NeqSim itself lacks the capability** (not a packaging gap, an engine gap) → implement it in Java with tests (`spotless:apply`) rather than working around it with ad-hoc Python; see `neqsim-troubleshooting` and the continuous-improvement rule in `AGENTS.md`. Both the MCP tools and the Python/Java API call the same NeqSim engine — the difference is packaging, not physics — but MCP additionally gives a stable JSON contract, schema validation, and provenance/quality-gate info (EOS, convergence, benchmark trust, standards) for free. | Use the **MCP server** (`mcp_neqsim_*` tools) when... | Use the **Python/Java API** (`import neqsim`, or Java in a checkout) when... | |---|---| | No dev environment is available (chat-only client, no terminal/Python/JVM) | Building a task notebook, multi-unit flowsheet, or anything with loops, custom logic, plotting, or state you inspect between steps | | The need is one bounded calculation a curated tool already covers (`runFlash`, `runProcess`, `getPhaseEnvelope`, `sizeEquipment`, `calculateStandard`, ...) | The calculation is not covered by a curated tool and `runCapability` routes it `inspect-only` (no safe generic invocation exists) | | You want the built-in provenance/quality-gate envelope (EOS, convergence, benchmark trust, standards) with no extra code | The run is long or iterative (Monte Carlo, sweeps, optimizer loops) — `runCapability`'s cooperative timeout is for short calls; a script has none | | A different agent/tool needs the result over a stable JSON contract (`composeWorkflow`, `composeMultiServerWorkflow`, cross-client interoperability) | You are inside the `/solve-task` workflow — task folders, validators, report generation, and the NeqSim Runner are Python-only, not exposed over MCP | | Doing bounded discovery of a capability before writing code (`runCapability` search, `inspectApi`) | You need direct object access (intermediate phase properties, custom equipment subclassing, mechanical design classes) beyond what any tool exposes | In a session with both available (e.g. this workspace), default to a curated MCP tool for a single quick calculation or lookup; switch to writing code as soon as the task needs more than one call, custom logic, or a deliverable (notebook, report, task folder). See `neqsim-task-workflow` skill §0.6 for the matrix by *environment* (workspace checkout / pip toolkit / chat-only) rather than by task. ## MCP Runtime Capability Routing When a calculation is not exposed by a curated MCP domain tool, use the generic runtime index before proposing a new tool: 1. Search with `runCapability({"action":"search","query":"<domain method>"})`. 2. Inspect the selected class with `inspectApi` to pin the deployed signature. 3. Follow the returned route: - `static-json`: invoke through `runCapability` with exact `className`, `methodName`, `parameterTypes`, and ordered JSON `arguments`. - `process-json`: build the stateful equipment through `runProcess`. - `inspect-only`: use a curated tool or add an explicit, reviewed adapter. Discovery is broader than execution by design. `runCapability` only invokes public static methods in approved domain packages with scalar, enum, or bounded-array JSON types; MCP runners, raw generic containers, oversized payloads, arbitrary objects, and instance methods are excluded. Its timeout uses cooperative Java interruption, so route long-running calculations through a curated runner or `runProcess`. Treat runtime presence as capability evidence, then check tests, benchmark trust, and standards before using the result for engineering decisions. ### MCP tool inputs: schema first, validate, then run Every calculation tool (`run*`, `sizeEquipment`, `designUtilities`, `calculateStandard`) has a tool-specific input schema whose field names and units mirror the runner exactly. Do not guess field names such as `flowRate_kg_hr` or `setPressure_barg`: 1. `getSchema("run_relief", "input")` — snake_case or camelCase (`runRelief`) both resolve. `required` / `allOf` (mode-dependent, e.g. `case: gas`) / `oneOf` tell you what to send. 2. `validateInput({"tool": "runRelief", "input": {...}})` — any tool; returns `SCHEMA_VIOLATION` issues naming the missing or mistyped field. Flash and process JSON may also be passed bare (auto-detected). 3. Run, then read `status`, `qualityGate`, `provenance.converged` and `warnings`. Process JSON pre-flight is strict: `UNRESOLVED_INLET` (an `inlet` naming no unit — feeds are units of `"type": "Stream"`), `MISPLACED_UNIT_PARAMETERS` (keys beside `properties`, e.g. `outletPressure_bara`; they are otherwise ignored and the unit runs on defaults) and `UNRECOGNIZED_INPUT_SHAPE` (no `process`/`areas`/`components`) are errors, and `runProcess` refuses to report success for a disconnected flowsheet. `runFlowAssurance` `hydrateRiskMap` requires `water` in the composition; an unavailable hydrate temperature is returned as `RESULT_NOT_AVAILABLE`, never as `LOW` risk (`RiskLevel.UNKNOWN` in Java). ## EOS Selection Guide | Fluid Type | Java Class | Mixing Rule | |-----------|-----------|-------------| | Dry/lean gas, simple HC | `SystemSrkEos` | `"classic"` | | General hydrocarbons, oil | `SystemPrEos` | `"classic"` | | **Matched to commercial simulator PR-LK** | **`SystemPrLeeKeslerEos`** | `"classic"` | | Water, MEG, methanol, polar | `SystemSrkCPAstatoil` | `10` (numeric) | | Custody transfer, fiscal metering | `SystemGERG2008Eos` | (none needed) | | Electrolyte systems, **hydrate with salt brine** | `SystemElectrolyteCPAstatoil` | `10` | | Volume-corrected SRK | `SystemSrkEosvolcor` | `"classic"` | **PR-LK vs PR78**: `SystemPrLeeKeslerEos` uses PR76 alpha for ALL ω: `m = 0.37464 + 1.54226ω − 0.26992ω²`. Standard `SystemPrEos1978` uses a modified cubic for ω > 0.49. Use PR-LK when matching commercial simulator models that use this EOS label. ## Fluid Creation (Required Sequence) ```java // 1. Create: temperature in KELVIN, pressure in bara SystemInterface fluid = new SystemSrkEos(273.15 + 25.0, 60.0); // 2. Add components (name, mole fraction) fluid.addComponent("methane", 0.85); fluid.addComponent("ethane", 0.10); fluid.addComponent("propane", 0.05); // 3. MANDATORY: set mixing rule — NEVER skip fluid.setMixingRule("classic"); // 4. Optional: multi-phase check for water/heavy systems fluid.setMultiPhaseCheck(true); ``` ## Oil Characterization (C7+ Fractions) ```java fluid.addTBPfraction("C7", 0.05, 92.0 / 1000, 0.727); // name, moleFrac, MW_kg/mol, density fluid.addTBPfraction("C8", 0.04, 104.0 / 1000, 0.749); fluid.addPlusFraction("C20", 0.02, 350.0 / 1000, 0.88); // no "+" in the name -- breaks parsing fluid.getCharacterization().configureLumping().model("standard").totalPseudoComponents(6).build(); fluid.getCharacterization().characterisePlusFraction(); fluid.setMixingRule("classic"); // MUST come AFTER characterisePlusFraction() -- see below ``` **Do not call `fluid.getCharacterization().getLumpingModel().setNumberOfLumpedComponents(n)` directly on the default ("standard") lumping model** -- confirmed (2026-10-12, PR review) that `StandardLumpingModel.generateLumpedComposition()` overwrites `numberOfLumpedComponents` with `numberOfPseudocomponents` (both default to 7) at generation time, so `setNumberOfLumpedComponents(6)` alone silently produces 7 pseudo-components, not 6. Use `configureLumping().model("standard").totalPseudoComponents(n).build()`, which sets `numberOfPseudocomponents` directly and is the value the generator actually reads. ## Loading Fluids from E300 Files NeqSim can read Eclipse E300-format fluid files with full component properties and binary interaction parameters: ```java // Load fluid from E300 file (returns SystemInterface with PR-EOS) SystemInterface fluid = EclipseFluidReadWrite.read("path/to/fluid.e300"); // Returns a PR-EOS fluid with all components, properties, and BIPs set ``` **Required E300 sections**: `CNAMES`, `TCRIT`, `PCRIT`, `ACF`, `MW`, `TBOIL`, `VCRIT`, `PARACHOR`, `SSHIFT`, `BIC`, `ZI`. **Optional E300 sections** (NeqSim parses and applies these): - `OMEGAA` / `OMEGAB` — per-component OmegaA/B overrides (applied after `init(0)`) - `BICS` — surface-condition BICs (parsed, same lower-triangular format as `BIC`) - `SSHIFTS` — surface-condition volume shift - `PEDERSEN` — activates Pedersen viscosity model **EOS keyword determines fluid class:** - `EOS\nSRK /` → `SystemSrkEos` - `EOS\nPR /\nPRCORR` → `SystemPrEos1978` - `EOS\nPR /\nPRLKCORR` → `SystemPrLeeKeslerEos` ← use for PR-LK matching - `EOS\nPR /` → `SystemPrEos` **CRITICAL**: The `BIC` section must ALWAYS be present. If omitted, NeqSim defaults to zero BIPs (no crash, but results may differ significantly from the source simulator). The `PARACHOR` section is also required — estimate unknown values with `4.0 * MW^0.77`. **Component name mapping**: `C1`→methane, `C2`→ethane, `C3`→propane, `iC4`→i-butane, `C4`→n-butane, `iC5`→i-pentane, `C5`→n-pentane, `C6`→n-hexane, `N2`→nitrogen, `CO2`→CO2, `H2O`→water. All other names are treated as TBP pseudo-fractions via `addTBPfraction()` — including aromatics (Benzene, Toluene, etc.). ```python # Python usage — auto-detects EOS from file from neqsim import jneqsim EclipseFluidReadWrite = jneqsim.thermo.util.readwrite.EclipseFluidReadWrite fluid = EclipseFluidReadWrite.read("path/to/fluid.e300") # Force a specific EOS regardless of what's in the file SystemPrLeeKeslerEos = jneqsim.thermo.system.SystemPrLeeKeslerEos target_fluid = SystemPrLeeKeslerEos(288.15, 1.01325) fluid = EclipseFluidReadWrite.read("path/to/fluid.e300", target_fluid) ``` **JSON process builder also supports PR-LK** via `"model": "PR_LK"`: ```json { "fluid": { "model": "PR_LK", "temperature": 288.15, "pressure": 50.0, ... } } ``` ## Flash Calculations and Property Retrieval ```java ThermodynamicOperations ops = new ThermodynamicOperations(fluid); ops.TPflash(); // CRITICAL: call initProperties() AFTER flash, BEFORE reading properties // init(3) alone does NOT initialize transport properties — they return ZERO fluid.initProperties(); // Bulk properties double density = fluid.getDensity("kg/m3"); double molarMass = fluid.getMolarMass("kg/mol"); double Z = fluid.getZ(); // Phase properties double gasDensity = fluid.getPhase("gas").getDensity("kg/m3"); double gasViscosity = fluid.getPhase("gas").getViscosity("kg/msec"); double gasThermalCond = fluid.getPhase("gas").getThermalConductivity("W/mK"); double gasCp = fluid.getPhase("gas").getCp("J/kgK"); // Phase checks int numPhases = fluid.getNumberOfPhases(); boolean hasGas = fluid.hasPhaseType("gas"); ``` ### Other Flash Types ```java ops.PHflash(enthalpy); // Pressure-Enthalpy ops.PSflash(entropy); // Pressure-Entropy ops.dewPointTemperatureFlash(); // Dew point temperature ops.bubblePointPressureFlash(); // Bubble point pressure ops.hydrateFormationTemperature(); // Hydrate T at given P ops.calcPTphaseEnvelope(); // Phase envelope ``` > **Saturation flashes can fail without throwing.** A failed continuation in > `dewPointPressureFlash()` / `bubblePointPressureFlash()` can leave a > non-physical pressure on the system and return normally. In a single run someone > notices; inside a Monte Carlo loop it silently poisons a percentile. Validate the > result on physical grounds instead of trusting the absence of an exception: > ```python > ops.dewPointPressureFlash() > p_dew = fluid.getPressure() > if not (10.0 < p_dew < 3.0 * p_reservoir): > p_dew = float("nan") # reject, do not propagate > ``` ### Re-flashing a characterised fluid many times (CRITICAL for loops) To take a phase's composition and flash it somewhere else — produced gas at each depletion step, a recycle stream, a Monte Carlo realization — **clone the already characterised fluid and overwrite its composition**. Do NOT rebuild it with `addTBPfraction`: that re-runs the TBP characterisation on every call. ```python probe = fluid.clone() probe.setTemperature(T_res + 273.15) probe.setPressure(p) ns.ThermodynamicOperations(probe).TPflash() probe.initProperties() gas_phase = probe.getPhase("gas") produced = fluid.clone() # keeps the characterisation produced.setMolarComposition( [gas_phase.getComponent(i).getx() for i in range(probe.getNumberOfComponents())]) produced.setTemperature(288.15) produced.setPressure(1.01325) ns.ThermodynamicOperations(produced).TPflash() # -> CGR, gas gravity, etc. ``` Measured on a 26-component P/A gas condensate with nine pressure nodes: the `addTBPfraction` rebuild cost **4.96 s** per realization against **0.97 s** for the clone-and-overwrite route, for bit-identical results. Over a 4000-member ensemble that is the difference between three hours and four minutes — i.e. between propagating uncertainty through the model and deciding not to. ## Unit Conventions | Quantity | Constructor default | Setter pattern | |----------|-------------------|----------------| | Temperature | **Kelvin** | `setTemperature(25.0, "C")` | | Pressure | **bara** | `setPressure(50.0, "bara")` |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub