Production platform process modeling patterns for NeqSim. USE WHEN: building full topside process models for oil & gas platforms (FPSO, fixed, semi-sub) from design documents, P&IDs, or operational data. Covers fluid creation with TBP fractions, multi-stage separation with recycles, recompression trains with compressor curves and anti-surge, export/injection compression, oil stabilization, scrubber liquid recovery, iteration strategies, and structured result extraction. Derived from 15+ production NCS platform models.
Production platform process modeling patterns for NeqSim. USE WHEN: building full topside process models for oil & gas platforms (FPSO, fixed, semi-sub) from design documents, P&IDs, or operational data. Covers fluid creation with TBP fractions, multi-stage separation with recycles, recompression trains with compressor curves and anti-surge, export/injection compression, oil stabilization, scrubber liquid recovery, iteration strategies, and structured result extraction. Derived from 15+ production NCS platform models.
Production platform process modeling patterns for NeqSim. USE WHEN: building full topside process models for oil & gas platforms (FPSO, fixed, semi-sub) from design documents, P&IDs, or operational data. Covers fluid creation with TBP fractions, multi-stage separation with recycles, recompression trains with compressor curves and anti-surge, export/injection compression, oil stabilization, scrubber liquid recovery, iteration strategies, and structured result extraction. Derived from 15+ production NCS platform models.
last_verified
2026-07-04
NeqSim Production Platform Modeling
Comprehensive patterns for building complete topside process simulations from
platform design documents. Derived from production-grade models of 15+ NCS
platforms (Åsgard A/B, Troll A/B, Grane, Castberg, Martin Linge, Gudrun, etc.)
in the NeqSim-dev-environment.
1. Architecture Overview
1.1 Standard Model Structure
Every production platform model follows the same architecture:
Best for: models with few recycles where NeqSim's internal solver handles convergence.
2. Fluid Creation
2.1 Composition with TBP Fractions (Recommended)
Production fluids almost always include heavy ends characterized as TBP
(True Boiling Point) fractions. The fluid_creator() pattern accepts a
composition dictionary with optional molar mass and density for TBP fractions:
from neqsim import jneqsim
deffluid_creator(composition: dict) -> "SystemInterface":
"""Create a NeqSim fluid from a composition dictionary.
Args:
composition: dict with keys:
- "component_name": list of component names
- "molar_composition[-]": list of mole fractions
- "molar_mass[kg/mol]": list (None for defined components)
- "relative_density[-]": list (None for defined components)
"""
fluid = jneqsim.thermo.system.SystemSrkEos(273.15 + 15.0, 1.01325)
names = composition["component_name"]
molfracs = composition["molar_composition[-]"]
molar_masses = composition["molar_mass[kg/mol]"]
rel_densities = composition["relative_density[-]"]
for i, name inenumerate(names):
if molar_masses[i] isnotNoneand rel_densities[i] isnotNone:
# TBP fraction — heavy hydrocarbon pseudo-component
fluid.addTBPfraction(
name, molfracs[i], molar_masses[i], rel_densities[i]
)
else:
# Defined component (methane, ethane, CO2, water, etc.)
fluid.addComponent(name, molfracs[i])
fluid.setMixingRule("classic")
fluid.setMultiPhaseCheck(True)
fluid.useVolumeCorrection(True)
return fluid
When a platform receives fluid from multiple wells/fields, define a base
fluid template with ALL possible TBP fractions, then clone and adjust per well:
# Base template with all TBP fraction definitions
base_fluid = jneqsim.thermo.system.SystemPrEos(273.15 + 30.0, 65.0)
base_fluid.addComponent("nitrogen", 0.08)
base_fluid.addComponent("CO2", 3.38)
base_fluid.addComponent("methane", 69.83)
# ... light HCs ...
base_fluid.addTBPfraction("C6_FieldA", 0.24, 84.99/1000, 695/1000)
base_fluid.addTBPfraction("C6_FieldB", 0.0, 84.0/1000, 684/1000)
# ... more TBP fractions for each field source ...
base_fluid.setMixingRule("classic")
# Per-well fluids via clone + setMolarComposition
well_fluid_A = base_fluid.clone()
well_fluid_A.setMolarComposition([0.108, 3.379, 69.5, ...]) # FieldA composition
well_fluid_B = base_fluid.clone()
well_fluid_B.setMolarComposition([0.095, 2.100, 72.1, ...]) # FieldB composition
Key rule: All wells must share the same component list (same TBP fraction
definitions). Use setMolarComposition() to set zero fractions for components
not present in a particular well.
3. Process Building Patterns
3.1 Pre-create Mixers Before Adding Streams
A critical pattern in production models: create StaticMixer objects
at the start, then add streams to them as equipment is built. This solves
the forward-reference problem (downstream mixer needs to exist before
upstream equipment creates its outlet stream).
operations = ProcessSystem()
# Pre-create all mixers FIRST (no streams yet)
inlet_oil_mixer = jneqsim.process.equipment.mixer.StaticMixer("Inlet oil mixer")
recomp_gas_mixer = jneqsim.process.equipment.mixer.StaticMixer("Recomp gas mixer")
export_manifold = jneqsim.process.equipment.mixer.StaticMixer("Export manifold")
recycle_oil_mixer = jneqsim.process.equipment.mixer.StaticMixer("Recycle from export")
# Build upstream equipment...# Then add their outlets to the pre-created mixers:
inlet_oil_mixer.addStream(oil_heater_test.getOutStream())
inlet_oil_mixer.addStream(oil_heater_prod.getOutStream())
# ... later, when you're ready to use the mixer:
operations.add(inlet_oil_mixer)
Important: Add the mixer to ProcessSystem AFTER all its inlet streams are
connected. All addStream() calls must happen before operations.add(mixer).
3.2 Multi-Stage Separation Train
Standard NCS platform separation: HP → MP → LP with oil heating/letdown between stages:
# === First Stage (HP) Separator ===
well_stream = Stream("Well Stream", well_fluid)
well_stream.setFlowRate(process_input.flow_rate, "kg/hr")
well_stream.setTemperature(process_input.inlet_temp, "C")
well_stream.setPressure(process_input.hp_pressure, "bara")
operations.add(well_stream)
hp_separator = ThreePhaseSeparator("HP Separator", well_stream)
operations.add(hp_separator)
# === Oil letdown to Second Stage ===
oil_heater_to_mp = Heater("Oil heater to MP", hp_separator.getLiquidOutStream())
oil_heater_to_mp.setOutTemperature(process_input.mp_temperature, "C")
oil_heater_to_mp.setOutPressure(process_input.mp_pressure, "bara")
operations.add(oil_heater_to_mp)
# Collect oil from multiple sources (see pre-created mixer pattern §3.1)
inlet_oil_mixer.addStream(oil_heater_to_mp.getOutStream())
# === Second Stage (MP) Separator ===
mp_separator = ThreePhaseSeparator("MP Separator", inlet_oil_mixer.getOutletStream())
operations.add(mp_separator)
# === Oil letdown to Third Stage ===
oil_to_lp = Heater("Oil to LP", mp_separator.getLiquidOutStream())
oil_to_lp.setOutTemperature(process_input.lp_temperature, "C")
oil_to_lp.setOutPressure(process_input.lp_pressure, "bara")
operations.add(oil_to_lp)
# === Third Stage (LP) Separator ===
lp_separator = Separator("LP Separator", oil_to_lp.getOutletStream())
operations.add(lp_separator)
# Oil export pump
oil_pump = Pump("Oil Export Pump", lp_separator.getLiquidOutStream())
oil_pump.setOutletPressure(process_input.oil_export_pressure + 1.01325) # barg to bara
operations.add(oil_pump)
3.2.1 Separator Physical Configuration via MechanicalDesign
Physical dimensions (vessel ID, nozzle sizes), internals (demister type,
inlet device), and design parameters (K-factor, retention time) are set via
SeparatorMechanicalDesign — NOT directly on the Separator. This follows
the same pattern used for wells, pipelines, and compressors in NeqSim.
Call initMechanicalDesign()after the process has been run() so the
design calculation has access to process conditions.
A common pattern uses a Heater with both outlet T and P set. This acts as
a T/P setter — useful for ensuring streams enter equipment at the correct
conditions (especially after mixing or before scrubbers):
Liquid knocked out in recompression scrubbers is recycled back to the
separation train. The standard pattern creates clone seed streams:
# Pre-create seed streams (cloned from the separator liquid for same composition)
recycle_seed_1 = mp_separator.getLiquidOutStream().clone()
recycle_seed_1.setName("Recycle from 1st stage scrubber")
recycle_seed_1.setFlowRate(1, "kg/hr") # Small initial flow for convergence
recycle_seed_1.setPressure(process_input.lp_pressure)
recycle_seed_1.setTemperature(process_input.lp_temperature, "C")
recycle_seed_2 = mp_separator.getLiquidOutStream().clone()
recycle_seed_2.setName("Recycle from 2nd stage scrubber")
recycle_seed_2.setFlowRate(1, "kg/hr")
recycle_seed_2.setPressure(process_input.lp_pressure)
recycle_seed_2.setTemperature(process_input.lp_temperature, "C")
# Add seeds to operations AND to the mixer that feeds the LP separator
operations.add(recycle_seed_1)
operations.add(recycle_seed_2)
rec_oil_mixer = StaticMixer("Recycle oil mixer")
rec_oil_mixer.addStream(oil_to_lp.getOutletStream()) # Main oil flow
rec_oil_mixer.addStream(recycle_seed_1)
rec_oil_mixer.addStream(recycle_seed_2)
operations.add(rec_oil_mixer)
# ... later, after building the recompression scrubbers:# Wire actual scrubber liquid to a Heater (TP setter), then to Recycle object
tp_set_scrub_liq_1 = Heater("TP set scrub liq 1", first_scrubber.getLiquidOutStream())
tp_set_scrub_liq_1.setOutTemperature(process_input.lp_temperature, "C")
tp_set_scrub_liq_1.setOutPressure(process_input.lp_pressure)
operations.add(tp_set_scrub_liq_1)
recycle_oil_1 = Recycle("Recycle oil 1")
recycle_oil_1.addStream(tp_set_scrub_liq_1.getOutletStream())
recycle_oil_1.setOutletStream(recycle_seed_1)
operations.add(recycle_oil_1)
Critical details:
Seed streams need small but non-zero flow (1 kg/hr) for numerical stability
Seed T, P must match the mixer/separator they feed into
Clone the composition from the appropriate location in the process
The Heater before the Recycle object acts as a T/P equalizer
4.2 Export/Injection Scrubber Liquid Recycle
Liquids from export and injection scrubbers are typically recycled back to
the MP (second stage) separator via a shared mixer:
# Pre-create mixer for all high-pressure scrubber liquids
mixer_recycle_from_export = StaticMixer("Recycle from export line")
# ... later, add scrubber liquid streams from export/injection/booster:if has_booster:
mixer_recycle_from_export.addStream(booster_scrubber.getLiquidOutStream())
if has_export:
mixer_recycle_from_export.addStream(export_scrubber.getLiquidOutStream())
if has_injection:
mixer_recycle_from_export.addStream(inj_scrubber_1.getLiquidOutStream())
mixer_recycle_from_export.addStream(inj_scrubber_2.getLiquidOutStream())
operations.add(mixer_recycle_from_export)
# Recycle back to MP separator via TP setter
tp_set_export_rec = Heater("TP set export rec", mixer_recycle_from_export.getOutletStream())
tp_set_export_rec.setOutTemperature(process_input.mp_temperature, "C")
tp_set_export_rec.setOutPressure(process_input.mp_pressure)
operations.add(tp_set_export_rec)
# Seed stream was added to inlet_oil_mixer earlier
recycle_from_export = Recycle("Recycle from export")
recycle_from_export.addStream(tp_set_export_rec.getOutletStream())
recycle_from_export.setOutletStream(export_recycle_seed) # Pre-created seed
operations.add(recycle_from_export)
4.3 Recycle Topology Summary
Typical NCS platform has 4-6 recycle loops:
Recycle
Source
Destination
Purpose
LP scrubber liquids (×3)
R1/R2/R3 scrubber liquids
LP separator inlet
Oil recovery
Export recycle
Export/injection/booster scrubber liquids
MP separator inlet
Oil recovery
Anti-surge R1
R1 compressor outlet
R1 compressor suction
Surge protection
Anti-surge R2
R2 compressor outlet
R2 compressor suction
Surge protection
Anti-surge R3
R3 compressor outlet
R3 compressor suction
Surge protection
Anti-surge export
Export compressor outlet
Export compressor suction
Surge protection
5. Recompression Train Pattern
5.1 Standard Stage (Cooler → Scrubber → Compressor → Anti-surge)
Each recompression stage follows this repeating pattern:
Three-phase separators for HP/MP (water), two-phase Separator for LP scrubbers
Oil TV P measurement: stream.TVP(20.0, "C") for true vapor pressure at 20°C
Run iterations: 25 run_step() calls or single threaded runAsThread() with timeout
Extract results: structured response helpers for every equipment type
Validate: mass balance, energy balance, hydrate temperatures above dewpoint
12. Notebook Template
For a Jupyter notebook implementation, see the complete starter in the
neqsim-notebook-patterns skill. The platform model follows the same
dual-boot setup cell pattern. Key additional imports: