- 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